Skip to main content

wowlab_engine_domain/rotation/bytecode/
backend.rs

1// #t(file: rust_padding) register allocation and emission statements stay contiguous in the repetitive backend implementation.
2// #t(file: rust_similar_fns) the typed register emitters deliberately mirror one another while preserving compile-time value types.
3// #t(file: rust_duplicate_strings) repeated invariant messages identify the impossible typed-register mismatch at each emission site.
4// #t(file: rust_multiple_inherent_impl) emission helpers follow the trait implementation so lifecycle methods remain first and the trait body stays uninterrupted.
5
6//! Bytecode-emission side of the [`RotationBackend`] lowering trait.
7
8use std::sync::Arc;
9
10use super::{
11    instruction::{
12        BoolRegister, BoolValue, FloatRegister, FloatValue, Instruction, InstructionAddress,
13        IntRegister, IntValue,
14    },
15    program::{BytecodeProgram, RegisterCounts},
16};
17#[cfg(feature = "rotation-optimizations")]
18use crate::rotation::buffer::CooldownSlot;
19use crate::rotation::{backend::RotationBackend, context::ContextSchema};
20
21#[derive(Debug)]
22pub(super) struct BytecodeBackend {
23    instructions: Vec<Instruction>,
24    bool_register_count: u32,
25    int_register_count: u32,
26    float_register_count: u32,
27    now_register: Option<FloatRegister>,
28}
29
30pub(super) struct BoolChain {
31    target: BoolValue,
32    jump_sites: Vec<InstructionAddress>,
33}
34
35#[cfg(feature = "rotation-optimizations")]
36pub(super) struct CastGuard {
37    failure_jumps: Vec<InstructionAddress>,
38}
39
40#[cfg(feature = "rotation-optimizations")]
41struct CooldownGuardSuffix {
42    instruction_count: usize,
43    cooldown_offset: usize,
44    bypass_aura_offset: Option<usize>,
45}
46
47#[cfg(feature = "rotation-optimizations")]
48struct TimestampGuardSuffix {
49    instruction_count: usize,
50    offset: usize,
51    expected_active: bool,
52}
53
54#[cfg(feature = "rotation-optimizations")]
55struct FloatComparisonGuardSuffix {
56    instruction_count: usize,
57    offset: usize,
58    rhs: f64,
59    comparison: super::instruction::FloatComparison,
60}
61
62#[cfg(feature = "rotation-optimizations")]
63const TIMESTAMP_ACTIVE_GUARD_INSTRUCTION_COUNT: usize = 4;
64#[cfg(feature = "rotation-optimizations")]
65const TIMESTAMP_INACTIVE_GUARD_INSTRUCTION_COUNT: usize =
66    TIMESTAMP_ACTIVE_GUARD_INSTRUCTION_COUNT + 1;
67#[cfg(feature = "rotation-optimizations")]
68const DIRECT_FLOAT_LITERAL_GUARD_INSTRUCTION_COUNT: usize = 2;
69#[cfg(feature = "rotation-optimizations")]
70const DIRECT_INTEGER_LITERAL_GUARD_INSTRUCTION_COUNT: usize = 3;
71
72#[cfg(feature = "rotation-optimizations")]
73fn float_comparison_parts(
74    instruction: Instruction,
75) -> Option<(
76    super::instruction::FloatComparison,
77    BoolRegister,
78    FloatValue,
79    FloatValue,
80)> {
81    use super::instruction::FloatComparison;
82
83    Some(match instruction {
84        Instruction::CmpGtF { target, lhs, rhs } => (FloatComparison::Gt, target, lhs, rhs),
85        Instruction::CmpGteF { target, lhs, rhs } => (FloatComparison::Gte, target, lhs, rhs),
86        Instruction::CmpLtF { target, lhs, rhs } => (FloatComparison::Lt, target, lhs, rhs),
87        Instruction::CmpLteF { target, lhs, rhs } => (FloatComparison::Lte, target, lhs, rhs),
88        Instruction::CmpEqF { target, lhs, rhs } => (FloatComparison::Eq, target, lhs, rhs),
89        Instruction::CmpNeF { target, lhs, rhs } => (FloatComparison::Ne, target, lhs, rhs),
90        _ => return None,
91    })
92}
93
94impl BytecodeBackend {
95    pub(super) fn new() -> Self {
96        Self {
97            instructions: Vec::new(),
98            bool_register_count: 0,
99            int_register_count: 0,
100            float_register_count: 0,
101            now_register: None,
102        }
103    }
104
105    pub(super) fn finish(self, schema: Arc<ContextSchema>) -> BytecodeProgram {
106        BytecodeProgram::new(
107            self.instructions,
108            RegisterCounts {
109                bools: self.bool_register_count,
110                ints: self.int_register_count,
111                floats: self.float_register_count,
112            },
113            schema,
114        )
115        .with_optional_now_register(self.now_register)
116    }
117
118    fn bool_register(&mut self) -> BoolValue {
119        BoolValue::Register(BoolRegister::next(&mut self.bool_register_count))
120    }
121
122    fn int_register(&mut self) -> IntValue {
123        IntValue::Register(IntRegister::next(&mut self.int_register_count))
124    }
125
126    fn float_register(&mut self) -> FloatValue {
127        FloatValue::Register(FloatRegister::next(&mut self.float_register_count))
128    }
129}
130
131#[cfg(all(test, feature = "rotation-optimizations"))]
132mod tests {
133    use googletest::prelude::*;
134
135    use super::*;
136
137    #[gtest]
138    fn repeated_now_reads_share_one_preinitialized_register() {
139        let mut backend = BytecodeBackend::new();
140
141        let first = backend.now();
142        let second = backend.now();
143
144        expect_that!(first, eq(second));
145        expect_that!(
146            &backend,
147            matches_pattern!(BytecodeBackend {
148                instructions: is_empty(),
149                float_register_count: eq(&1),
150                now_register: some(eq(&FloatRegister(0))),
151                ..
152            })
153        );
154    }
155}
156
157impl RotationBackend for BytecodeBackend {
158    type Bool = BoolValue;
159    type Int = IntValue;
160    type Float = FloatValue;
161    type AndChain = BoolChain;
162    type OrChain = BoolChain;
163    #[cfg(feature = "rotation-optimizations")]
164    type CastGuard = CastGuard;
165
166    fn const_bool(&mut self, v: bool) -> Self::Bool {
167        BoolValue::Constant(v)
168    }
169
170    fn const_i32(&mut self, v: i32) -> Self::Int {
171        IntValue::Constant(i64::from(v))
172    }
173
174    fn const_i64(&mut self, v: i64) -> Self::Int {
175        IntValue::Constant(v)
176    }
177
178    fn const_f64(&mut self, v: f64) -> Self::Float {
179        FloatValue::Constant(v)
180    }
181
182    fn load_bool(&mut self, offset: usize) -> Self::Bool {
183        let target = self.bool_register();
184        let BoolValue::Register(target_index) = target else {
185            unreachable!("fresh bool register must be a register value")
186        };
187
188        self.instructions.push(Instruction::LoadBool {
189            target: target_index,
190            offset,
191        });
192
193        target
194    }
195
196    fn load_i32(&mut self, offset: usize) -> Self::Int {
197        let target = self.int_register();
198        let IntValue::Register(target_index) = target else {
199            unreachable!("fresh int register must be a register value")
200        };
201
202        self.instructions.push(Instruction::LoadI32 {
203            target: target_index,
204            offset,
205        });
206
207        target
208    }
209
210    fn load_f64(&mut self, offset: usize) -> Self::Float {
211        let target = self.float_register();
212        let FloatValue::Register(target_index) = target else {
213            unreachable!("fresh float register must be a register value")
214        };
215
216        self.instructions.push(Instruction::LoadF64 {
217            target: target_index,
218            offset,
219        });
220
221        target
222    }
223
224    fn store_bool(&mut self, offset: usize, v: Self::Bool) {
225        self.instructions
226            .push(Instruction::StoreBool { offset, value: v });
227    }
228
229    fn store_i32(&mut self, offset: usize, v: Self::Int) {
230        self.instructions
231            .push(Instruction::StoreI32 { offset, value: v });
232    }
233
234    fn store_f64(&mut self, offset: usize, v: Self::Float) {
235        self.instructions
236            .push(Instruction::StoreF64 { offset, value: v });
237    }
238
239    fn now(&mut self) -> Self::Float {
240        #[cfg(feature = "rotation-optimizations")]
241        {
242            if let Some(register) = self.now_register {
243                return FloatValue::Register(register);
244            }
245
246            let target = self.float_register();
247            let FloatValue::Register(register) = target else {
248                unreachable!("fresh float register must be a register value")
249            };
250
251            self.now_register = Some(register);
252
253            target
254        }
255
256        #[cfg(not(feature = "rotation-optimizations"))]
257        {
258            let target = self.float_register();
259            let FloatValue::Register(target_index) = target else {
260                unreachable!("fresh float register must be a register value")
261            };
262
263            self.instructions.push(Instruction::Now {
264                target: target_index,
265            });
266
267            target
268        }
269    }
270
271    fn add_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
272        self.emit_float_binary(a, b, |target, lhs, rhs| Instruction::AddF {
273            target,
274            lhs,
275            rhs,
276        })
277    }
278
279    fn sub_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
280        self.emit_float_binary(a, b, |target, lhs, rhs| Instruction::SubF {
281            target,
282            lhs,
283            rhs,
284        })
285    }
286
287    fn mul_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
288        self.emit_float_binary(a, b, |target, lhs, rhs| Instruction::MulF {
289            target,
290            lhs,
291            rhs,
292        })
293    }
294
295    fn safe_div_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
296        self.emit_float_binary(a, b, |target, lhs, rhs| Instruction::SafeDivF {
297            target,
298            lhs,
299            rhs,
300        })
301    }
302
303    fn true_mod_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
304        self.emit_float_binary(a, b, |target, lhs, rhs| Instruction::TrueModF {
305            target,
306            lhs,
307            rhs,
308        })
309    }
310
311    fn min_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
312        self.emit_float_binary(a, b, |target, lhs, rhs| Instruction::MinF {
313            target,
314            lhs,
315            rhs,
316        })
317    }
318
319    fn max_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Float {
320        self.emit_float_binary(a, b, |target, lhs, rhs| Instruction::MaxF {
321            target,
322            lhs,
323            rhs,
324        })
325    }
326
327    fn floor_f(&mut self, v: Self::Float) -> Self::Float {
328        self.emit_float_unary(v, |target, value| Instruction::FloorF { target, value })
329    }
330
331    fn ceil_f(&mut self, v: Self::Float) -> Self::Float {
332        self.emit_float_unary(v, |target, value| Instruction::CeilF { target, value })
333    }
334
335    fn abs_f(&mut self, v: Self::Float) -> Self::Float {
336        self.emit_float_unary(v, |target, value| Instruction::AbsF { target, value })
337    }
338
339    fn cmp_gt_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Bool {
340        self.emit_float_compare(a, b, |target, lhs, rhs| Instruction::CmpGtF {
341            target,
342            lhs,
343            rhs,
344        })
345    }
346
347    fn cmp_gte_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Bool {
348        self.emit_float_compare(a, b, |target, lhs, rhs| Instruction::CmpGteF {
349            target,
350            lhs,
351            rhs,
352        })
353    }
354
355    fn cmp_lt_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Bool {
356        self.emit_float_compare(a, b, |target, lhs, rhs| Instruction::CmpLtF {
357            target,
358            lhs,
359            rhs,
360        })
361    }
362
363    fn cmp_lte_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Bool {
364        self.emit_float_compare(a, b, |target, lhs, rhs| Instruction::CmpLteF {
365            target,
366            lhs,
367            rhs,
368        })
369    }
370
371    fn cmp_eq_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Bool {
372        self.emit_float_compare(a, b, |target, lhs, rhs| Instruction::CmpEqF {
373            target,
374            lhs,
375            rhs,
376        })
377    }
378
379    fn cmp_ne_f(&mut self, a: Self::Float, b: Self::Float) -> Self::Bool {
380        self.emit_float_compare(a, b, |target, lhs, rhs| Instruction::CmpNeF {
381            target,
382            lhs,
383            rhs,
384        })
385    }
386
387    fn cmp_eq_i(&mut self, a: Self::Int, b: Self::Int) -> Self::Bool {
388        self.emit_int_compare(a, b, |target, lhs, rhs| Instruction::CmpEqI {
389            target,
390            lhs,
391            rhs,
392        })
393    }
394
395    fn cmp_ne_i(&mut self, a: Self::Int, b: Self::Int) -> Self::Bool {
396        self.emit_int_compare(a, b, |target, lhs, rhs| Instruction::CmpNeI {
397            target,
398            lhs,
399            rhs,
400        })
401    }
402
403    fn cmp_gt_i(&mut self, a: Self::Int, b: Self::Int) -> Self::Bool {
404        self.emit_int_compare(a, b, |target, lhs, rhs| Instruction::CmpGtI {
405            target,
406            lhs,
407            rhs,
408        })
409    }
410
411    fn cmp_gte_i(&mut self, a: Self::Int, b: Self::Int) -> Self::Bool {
412        self.emit_int_compare(a, b, |target, lhs, rhs| Instruction::CmpGteI {
413            target,
414            lhs,
415            rhs,
416        })
417    }
418
419    fn cmp_lt_i(&mut self, a: Self::Int, b: Self::Int) -> Self::Bool {
420        self.emit_int_compare(a, b, |target, lhs, rhs| Instruction::CmpLtI {
421            target,
422            lhs,
423            rhs,
424        })
425    }
426
427    fn cmp_lte_i(&mut self, a: Self::Int, b: Self::Int) -> Self::Bool {
428        self.emit_int_compare(a, b, |target, lhs, rhs| Instruction::CmpLteI {
429            target,
430            lhs,
431            rhs,
432        })
433    }
434
435    fn and_b(&mut self, a: Self::Bool, b: Self::Bool) -> Self::Bool {
436        self.emit_bool_binary(a, b, |target, lhs, rhs| Instruction::AndB {
437            target,
438            lhs,
439            rhs,
440        })
441    }
442
443    fn or_b(&mut self, a: Self::Bool, b: Self::Bool) -> Self::Bool {
444        self.emit_bool_binary(a, b, |target, lhs, rhs| Instruction::OrB {
445            target,
446            lhs,
447            rhs,
448        })
449    }
450
451    fn not_b(&mut self, a: Self::Bool) -> Self::Bool {
452        let target = self.bool_register();
453        let BoolValue::Register(target_index) = target else {
454            unreachable!("fresh bool register must be a register value")
455        };
456
457        self.instructions.push(Instruction::NotB {
458            target: target_index,
459            value: a,
460        });
461
462        target
463    }
464
465    fn begin_and(&mut self, first: Self::Bool) -> Self::AndChain {
466        self.begin_bool_chain(first, false)
467    }
468
469    fn push_and(&mut self, chain: &mut Self::AndChain, value: Self::Bool) {
470        self.push_bool_chain(chain, value, false);
471    }
472
473    fn finish_and(&mut self, chain: Self::AndChain) -> Self::Bool {
474        self.finish_bool_chain(chain, true)
475    }
476
477    fn begin_or(&mut self, first: Self::Bool) -> Self::OrChain {
478        self.begin_bool_chain(first, true)
479    }
480
481    fn push_or(&mut self, chain: &mut Self::OrChain, value: Self::Bool) {
482        self.push_bool_chain(chain, value, true);
483    }
484
485    fn finish_or(&mut self, chain: Self::OrChain) -> Self::Bool {
486        self.finish_bool_chain(chain, false)
487    }
488
489    #[cfg(feature = "rotation-optimizations")]
490    fn begin_cast_guard(&mut self, first: Self::Bool) -> Self::CastGuard {
491        let mut guard = CastGuard {
492            failure_jumps: Vec::new(),
493        };
494
495        self.push_cast_guard(&mut guard, first);
496
497        guard
498    }
499
500    #[cfg(feature = "rotation-optimizations")]
501    fn push_cast_guard(&mut self, guard: &mut Self::CastGuard, value: Self::Bool) {
502        if let BoolValue::Register(register) = value
503            && let Some(Instruction::LoadBool { target, offset }) =
504                self.instructions.last().copied()
505            && target == register
506        {
507            self.instructions.pop();
508            guard
509                .failure_jumps
510                .push(InstructionAddress::from_len(self.instructions.len()));
511            self.instructions.push(Instruction::JumpIfBufferBool {
512                offset,
513                target: InstructionAddress::placeholder(),
514                when_true: false,
515            });
516
517            return;
518        }
519
520        if let Some(suffix) = self.direct_float_comparison_guard_suffix(value) {
521            self.instructions.truncate(
522                self.instructions
523                    .len()
524                    .saturating_sub(suffix.instruction_count),
525            );
526            guard
527                .failure_jumps
528                .push(InstructionAddress::from_len(self.instructions.len()));
529            self.instructions
530                .push(Instruction::JumpIfBufferF64ComparisonFalse {
531                    offset: suffix.offset,
532                    rhs: suffix.rhs,
533                    comparison: suffix.comparison,
534                    target: InstructionAddress::placeholder(),
535                });
536
537            return;
538        }
539
540        if let Some(suffix) = self.cooldown_guard_suffix(value) {
541            self.instructions.truncate(
542                self.instructions
543                    .len()
544                    .saturating_sub(suffix.instruction_count),
545            );
546            guard
547                .failure_jumps
548                .push(InstructionAddress::from_len(self.instructions.len()));
549            self.instructions.push(Instruction::JumpIfCooldownNotReady {
550                cooldown_offset: suffix.cooldown_offset,
551                bypass_aura_offset: suffix.bypass_aura_offset,
552                target: InstructionAddress::placeholder(),
553            });
554
555            return;
556        }
557
558        if let Some(suffix) = self.timestamp_guard_suffix(value) {
559            self.instructions.truncate(
560                self.instructions
561                    .len()
562                    .saturating_sub(suffix.instruction_count),
563            );
564            guard
565                .failure_jumps
566                .push(InstructionAddress::from_len(self.instructions.len()));
567            self.instructions
568                .push(Instruction::JumpIfTimestampStateMismatch {
569                    offset: suffix.offset,
570                    expected_active: suffix.expected_active,
571                    target: InstructionAddress::placeholder(),
572                });
573
574            return;
575        }
576
577        guard
578            .failure_jumps
579            .push(InstructionAddress::from_len(self.instructions.len()));
580        self.instructions.push(Instruction::JumpIf {
581            condition: value,
582            target: InstructionAddress::placeholder(),
583            when_true: false,
584        });
585    }
586
587    #[cfg(feature = "rotation-optimizations")]
588    fn finish_cast_guard(
589        &mut self,
590        guard: Self::CastGuard,
591        spell_id: u32,
592        empower_rank: u8,
593    ) -> Self::Bool {
594        self.instructions.push(Instruction::ReturnCast {
595            spell_id,
596            empower_rank,
597        });
598        let next_action = InstructionAddress::from_len(self.instructions.len());
599
600        for jump_site in guard.failure_jumps {
601            let instruction = self
602                .instructions
603                .get_mut(jump_site.as_usize())
604                .expect("cast guard failure site must exist");
605            let (Instruction::JumpIf { target, .. }
606            | Instruction::JumpIfBufferBool { target, .. }
607            | Instruction::JumpIfCooldownNotReady { target, .. }
608            | Instruction::JumpIfTimestampStateMismatch { target, .. }
609            | Instruction::JumpIfBufferF64ComparisonFalse { target, .. }) = instruction
610            else {
611                unreachable!("cast guard failure site must contain a conditional jump")
612            };
613
614            *target = next_action;
615        }
616
617        BoolValue::Constant(false)
618    }
619
620    fn bool_to_float(&mut self, v: Self::Bool) -> Self::Float {
621        let target = self.float_register();
622        let FloatValue::Register(target_index) = target else {
623            unreachable!("fresh float register must be a register value")
624        };
625
626        self.instructions.push(Instruction::BoolToFloat {
627            target: target_index,
628            value: v,
629        });
630
631        target
632    }
633
634    fn bool_to_int(&mut self, v: Self::Bool) -> Self::Int {
635        let target = self.int_register();
636        let IntValue::Register(target_index) = target else {
637            unreachable!("fresh int register must be a register value")
638        };
639
640        self.instructions.push(Instruction::BoolToInt {
641            target: target_index,
642            value: v,
643        });
644
645        target
646    }
647
648    fn int_to_float(&mut self, v: Self::Int) -> Self::Float {
649        let target = self.float_register();
650        let FloatValue::Register(target_index) = target else {
651            unreachable!("fresh float register must be a register value")
652        };
653
654        self.instructions.push(Instruction::IntToFloat {
655            target: target_index,
656            value: v,
657        });
658
659        target
660    }
661
662    fn int_to_bool(&mut self, v: Self::Int) -> Self::Bool {
663        let target = self.bool_register();
664        let BoolValue::Register(target_index) = target else {
665            unreachable!("fresh bool register must be a register value")
666        };
667
668        self.instructions.push(Instruction::IntToBool {
669            target: target_index,
670            value: v,
671        });
672
673        target
674    }
675
676    fn float_to_bool(&mut self, v: Self::Float) -> Self::Bool {
677        let target = self.bool_register();
678        let BoolValue::Register(target_index) = target else {
679            unreachable!("fresh bool register must be a register value")
680        };
681
682        self.instructions.push(Instruction::FloatToBool {
683            target: target_index,
684            value: v,
685        });
686
687        target
688    }
689
690    fn float_to_int(&mut self, v: Self::Float) -> Self::Int {
691        let target = self.int_register();
692        let IntValue::Register(target_index) = target else {
693            unreachable!("fresh int register must be a register value")
694        };
695
696        self.instructions.push(Instruction::FloatToInt {
697            target: target_index,
698            value: v,
699        });
700
701        target
702    }
703
704    fn select_f(&mut self, cond: Self::Bool, t: Self::Float, f: Self::Float) -> Self::Float {
705        let target = self.float_register();
706        let FloatValue::Register(target_index) = target else {
707            unreachable!("fresh float register must be a register value")
708        };
709
710        self.instructions.push(Instruction::SelectF {
711            target: target_index,
712            condition: cond,
713            if_true: t,
714            if_false: f,
715        });
716
717        target
718    }
719
720    fn select_i(&mut self, cond: Self::Bool, t: Self::Int, f: Self::Int) -> Self::Int {
721        let target = self.int_register();
722        let IntValue::Register(target_index) = target else {
723            unreachable!("fresh int register must be a register value")
724        };
725
726        self.instructions.push(Instruction::SelectI {
727            target: target_index,
728            condition: cond,
729            if_true: t,
730            if_false: f,
731        });
732
733        target
734    }
735
736    fn select_b(&mut self, cond: Self::Bool, t: Self::Bool, f: Self::Bool) -> Self::Bool {
737        let target = self.bool_register();
738        let BoolValue::Register(target_index) = target else {
739            unreachable!("fresh bool register must be a register value")
740        };
741
742        self.instructions.push(Instruction::SelectB {
743            target: target_index,
744            condition: cond,
745            if_true: t,
746            if_false: f,
747        });
748
749        target
750    }
751
752    fn return_cast_if(&mut self, cond: Self::Bool, spell_id: u32, empower_rank: u8) {
753        self.instructions.push(Instruction::ReturnCastIf {
754            condition: cond,
755            spell_id,
756            empower_rank,
757        });
758    }
759
760    fn return_wait_if(&mut self, cond: Self::Bool, seconds: f32) {
761        self.instructions.push(Instruction::ReturnWaitIf {
762            condition: cond,
763            seconds,
764        });
765    }
766
767    fn return_pool_if(&mut self, cond: Self::Bool, target: f32) {
768        self.instructions.push(Instruction::ReturnPoolIf {
769            condition: cond,
770            target,
771        });
772    }
773
774    fn return_use_item_if(&mut self, cond: Self::Bool, gear_slot: u8, empower_rank: u8) {
775        self.instructions.push(Instruction::ReturnUseItemIf {
776            condition: cond,
777            gear_slot,
778            empower_rank,
779        });
780    }
781
782    fn return_none(&mut self) {
783        self.instructions.push(Instruction::ReturnNone);
784    }
785}
786
787impl BytecodeBackend {
788    #[cfg(feature = "rotation-optimizations")]
789    fn direct_float_comparison_guard_suffix(
790        &self,
791        value: BoolValue,
792    ) -> Option<FloatComparisonGuardSuffix> {
793        let BoolValue::Register(value_register) = value else {
794            return None;
795        };
796        let (comparison, target, lhs, rhs) =
797            float_comparison_parts(self.instructions.last().copied()?)?;
798
799        if target != value_register {
800            return None;
801        }
802        let FloatValue::Register(lhs_register) = lhs else {
803            return None;
804        };
805
806        match rhs {
807            FloatValue::Constant(rhs) => {
808                let Instruction::LoadF64 {
809                    target: loaded_lhs,
810                    offset,
811                } = self.instructions.get(
812                    self.instructions
813                        .len()
814                        .checked_sub(DIRECT_FLOAT_LITERAL_GUARD_INSTRUCTION_COUNT)?,
815                )?
816                else {
817                    return None;
818                };
819
820                (*loaded_lhs == lhs_register).then_some(FloatComparisonGuardSuffix {
821                    instruction_count: DIRECT_FLOAT_LITERAL_GUARD_INSTRUCTION_COUNT,
822                    offset: *offset,
823                    rhs,
824                    comparison,
825                })
826            }
827            FloatValue::Register(rhs_register) => {
828                let start = self
829                    .instructions
830                    .len()
831                    .checked_sub(DIRECT_INTEGER_LITERAL_GUARD_INSTRUCTION_COUNT)?;
832                let [
833                    Instruction::LoadF64 {
834                        target: loaded_lhs,
835                        offset,
836                    },
837                    Instruction::IntToFloat {
838                        target: converted_rhs,
839                        value: IntValue::Constant(rhs),
840                    },
841                    _,
842                ] = self.instructions.get(start..)?
843                else {
844                    return None;
845                };
846                let matches = *loaded_lhs == lhs_register && *converted_rhs == rhs_register;
847
848                matches.then_some(FloatComparisonGuardSuffix {
849                    instruction_count: DIRECT_INTEGER_LITERAL_GUARD_INSTRUCTION_COUNT,
850                    offset: *offset,
851                    rhs: wowlab_types::numeric::i64_to_f64(*rhs),
852                    comparison,
853                })
854            }
855        }
856    }
857
858    #[cfg(feature = "rotation-optimizations")]
859    fn timestamp_guard_suffix(&self, value: BoolValue) -> Option<TimestampGuardSuffix> {
860        if let BoolValue::Register(inactive_register) = value
861            && let Some(Instruction::NotB {
862                target,
863                value: BoolValue::Register(active_register),
864            }) = self.instructions.last()
865            && *target == inactive_register
866            && let Some(offset) = self.timestamp_active_suffix(
867                self.instructions.len().saturating_sub(1),
868                BoolValue::Register(*active_register),
869            )
870        {
871            return Some(TimestampGuardSuffix {
872                instruction_count: TIMESTAMP_INACTIVE_GUARD_INSTRUCTION_COUNT,
873                offset,
874                expected_active: false,
875            });
876        }
877
878        self.timestamp_active_suffix(self.instructions.len(), value)
879            .map(|offset| TimestampGuardSuffix {
880                instruction_count: TIMESTAMP_ACTIVE_GUARD_INSTRUCTION_COUNT,
881                offset,
882                expected_active: true,
883            })
884    }
885
886    #[cfg(feature = "rotation-optimizations")]
887    fn timestamp_active_suffix(&self, end: usize, value: BoolValue) -> Option<usize> {
888        let start = end.checked_sub(TIMESTAMP_ACTIVE_GUARD_INSTRUCTION_COUNT)?;
889        let [
890            Instruction::LoadF64 {
891                target: loaded_expiry,
892                offset,
893            },
894            Instruction::CmpGtF {
895                target: positive,
896                lhs: FloatValue::Register(positive_lhs),
897                rhs: FloatValue::Constant(zero),
898            },
899            Instruction::CmpLtF {
900                target: now_before_expiry,
901                lhs: FloatValue::Register(now_before_lhs),
902                rhs: FloatValue::Register(now_before_rhs),
903            },
904            Instruction::AndB {
905                target: active,
906                lhs: BoolValue::Register(and_lhs),
907                rhs: BoolValue::Register(and_rhs),
908            },
909        ] = self.instructions.get(start..end)?
910        else {
911            return None;
912        };
913        let now_register = self.now_register?;
914        let matches = [
915            value == BoolValue::Register(*active),
916            *positive_lhs == *loaded_expiry,
917            zero.to_bits() == 0.0_f64.to_bits(),
918            *now_before_lhs == now_register,
919            *now_before_rhs == *loaded_expiry,
920            *and_lhs == *positive,
921            *and_rhs == *now_before_expiry,
922        ]
923        .into_iter()
924        .all(std::convert::identity);
925
926        matches.then_some(*offset)
927    }
928
929    #[cfg(feature = "rotation-optimizations")]
930    fn cooldown_guard_suffix(&self, value: BoolValue) -> Option<CooldownGuardSuffix> {
931        self.cooldown_guard_suffix_without_bypass(value)
932            .or_else(|| self.cooldown_guard_suffix_with_bypass(value))
933    }
934
935    #[cfg(feature = "rotation-optimizations")]
936    fn cooldown_guard_suffix_without_bypass(
937        &self,
938        value: BoolValue,
939    ) -> Option<CooldownGuardSuffix> {
940        const INSTRUCTION_COUNT: usize = 8;
941
942        let start = self.instructions.len().checked_sub(INSTRUCTION_COUNT)?;
943        let [
944            Instruction::LoadF64 {
945                target: loaded_ready_at,
946                offset: cooldown_offset,
947            },
948            Instruction::LoadI32 {
949                target: loaded_current_charges,
950                offset: current_charges_offset,
951            },
952            Instruction::LoadI32 {
953                target: loaded_max_charges,
954                offset: max_charges_offset,
955            },
956            Instruction::CmpGtI {
957                target: charged,
958                lhs: IntValue::Register(charged_lhs),
959                rhs: IntValue::Constant(1),
960            },
961            Instruction::CmpGtI {
962                target: has_charge,
963                lhs: IntValue::Register(has_charge_lhs),
964                rhs: IntValue::Constant(0),
965            },
966            Instruction::CmpLteF {
967                target: time_ready,
968                lhs: FloatValue::Register(time_ready_lhs),
969                rhs: FloatValue::Register(time_ready_rhs),
970            },
971            Instruction::SelectB {
972                target: selected_ready,
973                condition: BoolValue::Register(select_condition),
974                if_true: BoolValue::Register(select_true),
975                if_false: BoolValue::Register(select_false),
976            },
977            Instruction::OrB {
978                target: result,
979                lhs: BoolValue::Register(or_lhs),
980                rhs: BoolValue::Constant(false),
981            },
982        ] = self.instructions.get(start..)?
983        else {
984            return None;
985        };
986        let now_register = self.now_register?;
987        let expected_current_charges = cooldown_offset
988            .checked_add(CooldownSlot::OFF_CURRENT_CHARGES - CooldownSlot::OFF_READY_AT)?;
989        let expected_max_charges = cooldown_offset
990            .checked_add(CooldownSlot::OFF_MAX_CHARGES - CooldownSlot::OFF_READY_AT)?;
991        let matches = [
992            value == BoolValue::Register(*result),
993            *charged_lhs == *loaded_max_charges,
994            *has_charge_lhs == *loaded_current_charges,
995            *time_ready_lhs == *loaded_ready_at,
996            *time_ready_rhs == now_register,
997            *select_condition == *charged,
998            *select_true == *has_charge,
999            *select_false == *time_ready,
1000            *or_lhs == *selected_ready,
1001            *current_charges_offset == expected_current_charges,
1002            *max_charges_offset == expected_max_charges,
1003        ]
1004        .into_iter()
1005        .all(std::convert::identity);
1006
1007        matches.then_some(CooldownGuardSuffix {
1008            instruction_count: INSTRUCTION_COUNT,
1009            cooldown_offset: *cooldown_offset,
1010            bypass_aura_offset: None,
1011        })
1012    }
1013
1014    #[cfg(feature = "rotation-optimizations")]
1015    fn cooldown_guard_suffix_with_bypass(&self, value: BoolValue) -> Option<CooldownGuardSuffix> {
1016        const INSTRUCTION_COUNT: usize = 10;
1017
1018        let start = self.instructions.len().checked_sub(INSTRUCTION_COUNT)?;
1019        let [
1020            Instruction::LoadF64 {
1021                target: loaded_ready_at,
1022                offset: cooldown_offset,
1023            },
1024            Instruction::LoadI32 {
1025                target: loaded_current_charges,
1026                offset: current_charges_offset,
1027            },
1028            Instruction::LoadI32 {
1029                target: loaded_max_charges,
1030                offset: max_charges_offset,
1031            },
1032            Instruction::LoadF64 {
1033                target: loaded_bypass_expires,
1034                offset: bypass_aura_offset,
1035            },
1036            Instruction::CmpGtF {
1037                target: bypass_active,
1038                lhs: FloatValue::Register(bypass_lhs),
1039                rhs: FloatValue::Register(bypass_rhs),
1040            },
1041            Instruction::CmpGtI {
1042                target: charged,
1043                lhs: IntValue::Register(charged_lhs),
1044                rhs: IntValue::Constant(1),
1045            },
1046            Instruction::CmpGtI {
1047                target: has_charge,
1048                lhs: IntValue::Register(has_charge_lhs),
1049                rhs: IntValue::Constant(0),
1050            },
1051            Instruction::CmpLteF {
1052                target: time_ready,
1053                lhs: FloatValue::Register(time_ready_lhs),
1054                rhs: FloatValue::Register(time_ready_rhs),
1055            },
1056            Instruction::SelectB {
1057                target: selected_ready,
1058                condition: BoolValue::Register(select_condition),
1059                if_true: BoolValue::Register(select_true),
1060                if_false: BoolValue::Register(select_false),
1061            },
1062            Instruction::OrB {
1063                target: result,
1064                lhs: BoolValue::Register(or_lhs),
1065                rhs: BoolValue::Register(or_rhs),
1066            },
1067        ] = self.instructions.get(start..)?
1068        else {
1069            return None;
1070        };
1071        let now_register = self.now_register?;
1072        let expected_current_charges = cooldown_offset
1073            .checked_add(CooldownSlot::OFF_CURRENT_CHARGES - CooldownSlot::OFF_READY_AT)?;
1074        let expected_max_charges = cooldown_offset
1075            .checked_add(CooldownSlot::OFF_MAX_CHARGES - CooldownSlot::OFF_READY_AT)?;
1076        let matches = [
1077            value == BoolValue::Register(*result),
1078            *bypass_lhs == *loaded_bypass_expires,
1079            *bypass_rhs == now_register,
1080            *or_rhs == *bypass_active,
1081            *charged_lhs == *loaded_max_charges,
1082            *has_charge_lhs == *loaded_current_charges,
1083            *time_ready_lhs == *loaded_ready_at,
1084            *time_ready_rhs == now_register,
1085            *select_condition == *charged,
1086            *select_true == *has_charge,
1087            *select_false == *time_ready,
1088            *or_lhs == *selected_ready,
1089            *current_charges_offset == expected_current_charges,
1090            *max_charges_offset == expected_max_charges,
1091        ]
1092        .into_iter()
1093        .all(std::convert::identity);
1094
1095        matches.then_some(CooldownGuardSuffix {
1096            instruction_count: INSTRUCTION_COUNT,
1097            cooldown_offset: *cooldown_offset,
1098            bypass_aura_offset: Some(*bypass_aura_offset),
1099        })
1100    }
1101
1102    fn begin_bool_chain(&mut self, first: BoolValue, short_circuit_value: bool) -> BoolChain {
1103        let target = self.bool_register();
1104        let BoolValue::Register(target_index) = target else {
1105            unreachable!("fresh bool register must be a register value")
1106        };
1107
1108        self.instructions.push(Instruction::SetBool {
1109            target: target_index,
1110            value: short_circuit_value,
1111        });
1112        let mut chain = BoolChain {
1113            target,
1114            jump_sites: Vec::new(),
1115        };
1116
1117        self.push_bool_chain(&mut chain, first, short_circuit_value);
1118
1119        chain
1120    }
1121
1122    fn push_bool_chain(
1123        &mut self,
1124        chain: &mut BoolChain,
1125        value: BoolValue,
1126        short_circuit_value: bool,
1127    ) {
1128        chain
1129            .jump_sites
1130            .push(InstructionAddress::from_len(self.instructions.len()));
1131        self.instructions.push(Instruction::JumpIf {
1132            condition: value,
1133            target: InstructionAddress::placeholder(),
1134            when_true: short_circuit_value,
1135        });
1136    }
1137
1138    fn finish_bool_chain(&mut self, chain: BoolChain, completed_value: bool) -> BoolValue {
1139        let BoolValue::Register(target_index) = chain.target else {
1140            unreachable!("bool chain target must be a register value")
1141        };
1142
1143        self.instructions.push(Instruction::SetBool {
1144            target: target_index,
1145            value: completed_value,
1146        });
1147        let end = InstructionAddress::from_len(self.instructions.len());
1148
1149        for jump_site in chain.jump_sites {
1150            // BOUNDS: each site is captured from `instructions.len()` immediately before its `JumpIf` is pushed.
1151            let Instruction::JumpIf { target, .. } = &mut self.instructions[jump_site.as_usize()]
1152            else {
1153                unreachable!("bool chain jump site must contain a conditional jump")
1154            };
1155
1156            *target = end;
1157        }
1158
1159        chain.target
1160    }
1161
1162    fn emit_float_binary(
1163        &mut self,
1164        lhs: FloatValue,
1165        rhs: FloatValue,
1166        instruction: impl FnOnce(FloatRegister, FloatValue, FloatValue) -> Instruction,
1167    ) -> FloatValue {
1168        let target = self.float_register();
1169        let FloatValue::Register(target_index) = target else {
1170            unreachable!("fresh float register must be a register value")
1171        };
1172
1173        self.instructions.push(instruction(target_index, lhs, rhs));
1174
1175        target
1176    }
1177
1178    fn emit_float_unary(
1179        &mut self,
1180        value: FloatValue,
1181        instruction: impl FnOnce(FloatRegister, FloatValue) -> Instruction,
1182    ) -> FloatValue {
1183        let target = self.float_register();
1184        let FloatValue::Register(target_index) = target else {
1185            unreachable!("fresh float register must be a register value")
1186        };
1187
1188        self.instructions.push(instruction(target_index, value));
1189
1190        target
1191    }
1192
1193    fn emit_float_compare(
1194        &mut self,
1195        lhs: FloatValue,
1196        rhs: FloatValue,
1197        instruction: impl FnOnce(BoolRegister, FloatValue, FloatValue) -> Instruction,
1198    ) -> BoolValue {
1199        let target = self.bool_register();
1200        let BoolValue::Register(target_index) = target else {
1201            unreachable!("fresh bool register must be a register value")
1202        };
1203
1204        self.instructions.push(instruction(target_index, lhs, rhs));
1205
1206        target
1207    }
1208
1209    fn emit_int_compare(
1210        &mut self,
1211        lhs: IntValue,
1212        rhs: IntValue,
1213        instruction: impl FnOnce(BoolRegister, IntValue, IntValue) -> Instruction,
1214    ) -> BoolValue {
1215        let target = self.bool_register();
1216        let BoolValue::Register(target_index) = target else {
1217            unreachable!("fresh bool register must be a register value")
1218        };
1219
1220        self.instructions.push(instruction(target_index, lhs, rhs));
1221
1222        target
1223    }
1224
1225    fn emit_bool_binary(
1226        &mut self,
1227        lhs: BoolValue,
1228        rhs: BoolValue,
1229        instruction: impl FnOnce(BoolRegister, BoolValue, BoolValue) -> Instruction,
1230    ) -> BoolValue {
1231        let target = self.bool_register();
1232        let BoolValue::Register(target_index) = target else {
1233            unreachable!("fresh bool register must be a register value")
1234        };
1235
1236        self.instructions.push(instruction(target_index, lhs, rhs));
1237
1238        target
1239    }
1240}