1use 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 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}