1use wowlab_types::{
9 numeric::{float_eq, float_ne, safe_div, true_mod},
10 sim::SpellIdx,
11};
12
13#[cfg(feature = "rotation-optimizations")]
14use super::instruction::FloatComparison;
15use super::{
16 instruction::{BoolRegister, BoolValue, FloatValue, Instruction, IntValue},
17 program::BytecodeProgram,
18};
19#[cfg(feature = "rotation-optimizations")]
20use crate::rotation::{
21 buffer::CooldownSlot,
22 lower::ops::{ScalarEvalOps, eval_cooldown_ready},
23};
24use crate::rotation::{buffer::DenseBuffer, result::EvalResult};
25
26#[derive(Debug)]
27#[expect(
28 clippy::struct_field_names,
29 reason = "the suffix identifies each typed register bank consistently"
30)]
31pub(super) struct BytecodeRuntime {
32 bool_registers: Box<[u8]>,
33 int_registers: Box<[i64]>,
34 float_registers: Box<[f64]>,
35}
36
37impl BytecodeRuntime {
38 pub(super) fn new(program: &BytecodeProgram) -> Self {
39 let counts = program.register_counts();
40
41 Self {
42 bool_registers: vec![0; counts.bools()].into_boxed_slice(),
43 int_registers: vec![0; counts.ints()].into_boxed_slice(),
44 float_registers: vec![0.0; counts.floats()].into_boxed_slice(),
45 }
46 }
47
48 #[inline]
51 pub(super) fn execute(
52 &mut self,
53 program: &BytecodeProgram,
54 buffer: &mut DenseBuffer,
55 now: f64,
56 ) -> EvalResult {
57 if let Some(register) = program.now_register() {
58 self.float_registers[register.as_usize()] = now;
59 }
60
61 let mut instruction_pointer = 0;
62
63 while instruction_pointer < program.instructions.len() {
64 let instruction = program.instructions[instruction_pointer];
65
66 instruction_pointer += 1;
67
68 match instruction {
69 Instruction::LoadBool { target, offset } => {
70 let value = bytemuck::pod_read_unaligned::<i32>(
71 &buffer.bytes()[offset..offset + size_of::<i32>()],
72 ) != 0;
73
74 self.store_bool_register(target, value);
75 }
76 Instruction::LoadI32 { target, offset } => {
77 self.int_registers[target.as_usize()] =
78 i64::from(bytemuck::pod_read_unaligned::<i32>(
79 &buffer.bytes()[offset..offset + size_of::<i32>()],
80 ));
81 }
82 Instruction::LoadF64 { target, offset } => {
83 self.float_registers[target.as_usize()] = bytemuck::pod_read_unaligned::<f64>(
84 &buffer.bytes()[offset..offset + size_of::<f64>()],
85 );
86 }
87 Instruction::StoreBool { offset, value } => {
88 let bytes = i32::from(self.bool_value(value)).to_ne_bytes();
89
90 buffer.bytes_mut()[offset..offset + bytes.len()].copy_from_slice(&bytes);
91 }
92 Instruction::StoreI32 { offset, value } => {
93 let bytes =
94 crate::numeric::wrapping_i64_to_i32(self.int_value(value)).to_ne_bytes();
95
96 buffer.bytes_mut()[offset..offset + bytes.len()].copy_from_slice(&bytes);
97 }
98 Instruction::StoreF64 { offset, value } => {
99 let bytes = self.float_value(value).to_ne_bytes();
100
101 buffer.bytes_mut()[offset..offset + bytes.len()].copy_from_slice(&bytes);
102 }
103 #[cfg(not(feature = "rotation-optimizations"))]
104 Instruction::Now { target } => self.float_registers[target.as_usize()] = now,
105 Instruction::AddF { target, lhs, rhs } => {
106 self.float_registers[target.as_usize()] =
107 self.float_value(lhs) + self.float_value(rhs);
108 }
109 Instruction::SubF { target, lhs, rhs } => {
110 self.float_registers[target.as_usize()] =
111 self.float_value(lhs) - self.float_value(rhs);
112 }
113 Instruction::MulF { target, lhs, rhs } => {
114 self.float_registers[target.as_usize()] =
115 self.float_value(lhs) * self.float_value(rhs);
116 }
117 Instruction::SafeDivF { target, lhs, rhs } => {
118 self.float_registers[target.as_usize()] =
119 safe_div(self.float_value(lhs), self.float_value(rhs));
120 }
121 Instruction::TrueModF { target, lhs, rhs } => {
122 self.float_registers[target.as_usize()] =
123 true_mod(self.float_value(lhs), self.float_value(rhs));
124 }
125 Instruction::MinF { target, lhs, rhs } => {
126 self.float_registers[target.as_usize()] =
127 self.float_value(lhs).min(self.float_value(rhs));
128 }
129 Instruction::MaxF { target, lhs, rhs } => {
130 self.float_registers[target.as_usize()] =
131 self.float_value(lhs).max(self.float_value(rhs));
132 }
133 Instruction::FloorF { target, value } => {
134 self.float_registers[target.as_usize()] = self.float_value(value).floor();
135 }
136 Instruction::CeilF { target, value } => {
137 self.float_registers[target.as_usize()] = self.float_value(value).ceil();
138 }
139 Instruction::AbsF { target, value } => {
140 self.float_registers[target.as_usize()] = self.float_value(value).abs();
141 }
142 Instruction::CmpGtF { target, lhs, rhs } => {
143 let value = self.float_value(lhs) > self.float_value(rhs);
144
145 self.store_bool_register(target, value);
146 }
147 Instruction::CmpGteF { target, lhs, rhs } => {
148 let value = self.float_value(lhs) >= self.float_value(rhs);
149
150 self.store_bool_register(target, value);
151 }
152 Instruction::CmpLtF { target, lhs, rhs } => {
153 let value = self.float_value(lhs) < self.float_value(rhs);
154
155 self.store_bool_register(target, value);
156 }
157 Instruction::CmpLteF { target, lhs, rhs } => {
158 let value = self.float_value(lhs) <= self.float_value(rhs);
159
160 self.store_bool_register(target, value);
161 }
162 Instruction::CmpEqF { target, lhs, rhs } => {
163 let value = float_eq(self.float_value(lhs), self.float_value(rhs));
164
165 self.store_bool_register(target, value);
166 }
167 Instruction::CmpNeF { target, lhs, rhs } => {
168 let value = float_ne(self.float_value(lhs), self.float_value(rhs));
169
170 self.store_bool_register(target, value);
171 }
172 Instruction::CmpEqI { target, lhs, rhs } => {
173 let value = self.int_value(lhs) == self.int_value(rhs);
174
175 self.store_bool_register(target, value);
176 }
177 Instruction::CmpNeI { target, lhs, rhs } => {
178 let value = self.int_value(lhs) != self.int_value(rhs);
179
180 self.store_bool_register(target, value);
181 }
182 Instruction::CmpGtI { target, lhs, rhs } => {
183 let value = self.int_value(lhs) > self.int_value(rhs);
184
185 self.store_bool_register(target, value);
186 }
187 Instruction::CmpGteI { target, lhs, rhs } => {
188 let value = self.int_value(lhs) >= self.int_value(rhs);
189
190 self.store_bool_register(target, value);
191 }
192 Instruction::CmpLtI { target, lhs, rhs } => {
193 let value = self.int_value(lhs) < self.int_value(rhs);
194
195 self.store_bool_register(target, value);
196 }
197 Instruction::CmpLteI { target, lhs, rhs } => {
198 let value = self.int_value(lhs) <= self.int_value(rhs);
199
200 self.store_bool_register(target, value);
201 }
202 Instruction::AndB { target, lhs, rhs } => {
203 let value = self.bool_value(lhs) && self.bool_value(rhs);
204
205 self.store_bool_register(target, value);
206 }
207 Instruction::OrB { target, lhs, rhs } => {
208 let value = self.bool_value(lhs) || self.bool_value(rhs);
209
210 self.store_bool_register(target, value);
211 }
212 Instruction::SetBool { target, value } => {
213 self.store_bool_register(target, value);
214 }
215 Instruction::JumpIf {
216 condition,
217 target,
218 when_true,
219 } => {
220 if self.bool_value(condition) == when_true {
221 instruction_pointer = target.as_usize();
222 }
223 }
224 #[cfg(feature = "rotation-optimizations")]
225 Instruction::JumpIfBufferBool {
226 offset,
227 target,
228 when_true,
229 } => {
230 let value = bytemuck::pod_read_unaligned::<i32>(
231 &buffer.bytes()[offset..offset + size_of::<i32>()],
232 ) != 0;
233
234 if value == when_true {
235 instruction_pointer = target.as_usize();
236 }
237 }
238 #[cfg(feature = "rotation-optimizations")]
239 Instruction::JumpIfCooldownNotReady {
240 cooldown_offset,
241 bypass_aura_offset,
242 target,
243 } => {
244 let bytes = buffer.bytes();
245 let ready_at = bytemuck::pod_read_unaligned::<f64>(
246 &bytes[cooldown_offset..cooldown_offset + size_of::<f64>()],
247 );
248 let current_charges_offset = cooldown_offset
249 + CooldownSlot::OFF_CURRENT_CHARGES
250 - CooldownSlot::OFF_READY_AT;
251 let current_charges = bytemuck::pod_read_unaligned::<i32>(
252 &bytes[current_charges_offset..current_charges_offset + size_of::<i32>()],
253 );
254 let max_charges_offset = cooldown_offset + CooldownSlot::OFF_MAX_CHARGES
255 - CooldownSlot::OFF_READY_AT;
256 let max_charges = bytemuck::pod_read_unaligned::<i32>(
257 &bytes[max_charges_offset..max_charges_offset + size_of::<i32>()],
258 );
259 let bypass_active = bypass_aura_offset.is_some_and(|offset| {
260 bytemuck::pod_read_unaligned::<f64>(
261 &bytes[offset..offset + size_of::<f64>()],
262 ) > now
263 });
264 let ready = eval_cooldown_ready(
265 &mut ScalarEvalOps,
266 ready_at,
267 current_charges,
268 max_charges,
269 now,
270 bypass_active,
271 );
272
273 if !ready {
274 instruction_pointer = target.as_usize();
275 }
276 }
277 #[cfg(feature = "rotation-optimizations")]
278 Instruction::JumpIfTimestampStateMismatch {
279 offset,
280 expected_active,
281 target,
282 } => {
283 let expires_at = bytemuck::pod_read_unaligned::<f64>(
284 &buffer.bytes()[offset..offset + size_of::<f64>()],
285 );
286 let positive = expires_at > 0.0;
287 let now_before_expiry = now < expires_at;
288 let active = positive & now_before_expiry;
289
290 if active != expected_active {
291 instruction_pointer = target.as_usize();
292 }
293 }
294 #[cfg(feature = "rotation-optimizations")]
295 Instruction::JumpIfBufferF64ComparisonFalse {
296 offset,
297 rhs,
298 comparison,
299 target,
300 } => {
301 let lhs = bytemuck::pod_read_unaligned::<f64>(
302 &buffer.bytes()[offset..offset + size_of::<f64>()],
303 );
304 let result = match comparison {
305 FloatComparison::Gt => lhs > rhs,
306 FloatComparison::Gte => lhs >= rhs,
307 FloatComparison::Lt => lhs < rhs,
308 FloatComparison::Lte => lhs <= rhs,
309 FloatComparison::Eq => float_eq(lhs, rhs),
310 FloatComparison::Ne => float_ne(lhs, rhs),
311 };
312
313 if !result {
314 instruction_pointer = target.as_usize();
315 }
316 }
317 Instruction::NotB { target, value } => {
318 let value = !self.bool_value(value);
319
320 self.store_bool_register(target, value);
321 }
322 Instruction::BoolToFloat { target, value } => {
323 self.float_registers[target.as_usize()] =
324 if self.bool_value(value) { 1.0 } else { 0.0 };
325 }
326 Instruction::BoolToInt { target, value } => {
327 self.int_registers[target.as_usize()] = i64::from(self.bool_value(value));
328 }
329 Instruction::IntToFloat { target, value } => {
330 self.float_registers[target.as_usize()] =
331 wowlab_types::numeric::i64_to_f64(self.int_value(value));
332 }
333 Instruction::IntToBool { target, value } => {
334 let value = self.int_value(value) != 0;
335
336 self.store_bool_register(target, value);
337 }
338 Instruction::FloatToBool { target, value } => {
339 let value = self.float_value(value) != 0.0;
340
341 self.store_bool_register(target, value);
342 }
343 Instruction::FloatToInt { target, value } => {
344 self.int_registers[target.as_usize()] =
345 wowlab_types::numeric::f64_to_i64_saturating_trunc(self.float_value(value));
346 }
347 Instruction::SelectF {
348 target,
349 condition,
350 if_true,
351 if_false,
352 } => {
353 self.float_registers[target.as_usize()] = if self.bool_value(condition) {
354 self.float_value(if_true)
355 } else {
356 self.float_value(if_false)
357 };
358 }
359 Instruction::SelectI {
360 target,
361 condition,
362 if_true,
363 if_false,
364 } => {
365 self.int_registers[target.as_usize()] = if self.bool_value(condition) {
366 self.int_value(if_true)
367 } else {
368 self.int_value(if_false)
369 };
370 }
371 Instruction::SelectB {
372 target,
373 condition,
374 if_true,
375 if_false,
376 } => {
377 let value = if self.bool_value(condition) {
378 self.bool_value(if_true)
379 } else {
380 self.bool_value(if_false)
381 };
382
383 self.store_bool_register(target, value);
384 }
385 #[cfg(feature = "rotation-optimizations")]
386 Instruction::ReturnCast {
387 spell_id,
388 empower_rank,
389 } => return EvalResult::cast(SpellIdx(spell_id), empower_rank),
390 Instruction::ReturnCastIf {
391 condition,
392 spell_id,
393 empower_rank,
394 } => {
395 if self.bool_value(condition) {
396 return EvalResult::cast(SpellIdx(spell_id), empower_rank);
397 }
398 }
399 Instruction::ReturnWaitIf { condition, seconds } => {
400 if self.bool_value(condition) {
401 return EvalResult::wait(seconds);
402 }
403 }
404 Instruction::ReturnPoolIf { condition, target } => {
405 if self.bool_value(condition) {
406 return EvalResult::pool(target);
407 }
408 }
409 Instruction::ReturnUseItemIf {
410 condition,
411 gear_slot,
412 empower_rank,
413 } => {
414 if self.bool_value(condition) {
415 return EvalResult::use_item_raw(gear_slot, empower_rank);
416 }
417 }
418 Instruction::ReturnNone => return EvalResult::NONE,
419 }
420 }
421
422 EvalResult::NONE
423 }
424
425 #[inline]
426 fn bool_value(&self, value: BoolValue) -> bool {
427 match value {
428 BoolValue::Constant(value) => value,
429 BoolValue::Register(register) => self.bool_registers[register.as_usize()] != 0,
430 }
431 }
432
433 #[inline]
434 fn int_value(&self, value: IntValue) -> i64 {
435 match value {
436 IntValue::Constant(value) => value,
437 IntValue::Register(register) => self.int_registers[register.as_usize()],
438 }
439 }
440
441 #[inline]
442 fn float_value(&self, value: FloatValue) -> f64 {
443 match value {
444 FloatValue::Constant(value) => value,
445 FloatValue::Register(register) => self.float_registers[register.as_usize()],
446 }
447 }
448
449 #[inline]
450 fn store_bool_register(&mut self, target: BoolRegister, value: bool) {
451 self.bool_registers[target.as_usize()] = u8::from(value);
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use std::sync::Arc;
458
459 use googletest::prelude::*;
460 use wowlab_types::sim::Rotation;
461
462 use super::*;
463 use crate::rotation::{
464 BufferRequirements, RotationPreparation, SpecResolver,
465 bytecode::{
466 instruction::{FloatRegister, InstructionAddress, IntRegister},
467 program::RegisterCounts,
468 },
469 };
470
471 fn test_program(instructions: Vec<Instruction>, counts: RegisterCounts) -> BytecodeProgram {
472 let prepared = RotationPreparation::prepare(
473 &Rotation::empty(),
474 &SpecResolver::new("bytecode-runtime"),
475 )
476 .expect("prepare empty rotation")
477 .bind(&BufferRequirements::new())
478 .expect("bind empty schema");
479
480 BytecodeProgram::new(instructions, counts, Arc::clone(prepared.schema()))
481 }
482
483 #[cfg(feature = "rotation-optimizations")]
484 fn test_program_with_now(
485 instructions: Vec<Instruction>,
486 counts: RegisterCounts,
487 now_register: FloatRegister,
488 ) -> BytecodeProgram {
489 test_program(instructions, counts).with_optional_now_register(Some(now_register))
490 }
491
492 #[gtest]
493 fn every_instruction_executes_with_typed_register_banks() {
494 let b = |index| BoolRegister(index);
495 let i = |index| IntRegister(index);
496 let f = |index| FloatRegister(index);
497 let instructions = vec![
498 Instruction::StoreBool {
499 offset: 0,
500 value: BoolValue::Constant(true),
501 },
502 Instruction::LoadBool {
503 target: b(0),
504 offset: 0,
505 },
506 Instruction::StoreI32 {
507 offset: 8,
508 value: IntValue::Constant(7),
509 },
510 Instruction::LoadI32 {
511 target: i(0),
512 offset: 8,
513 },
514 Instruction::StoreF64 {
515 offset: 16,
516 value: FloatValue::Constant(-2.5),
517 },
518 Instruction::LoadF64 {
519 target: f(0),
520 offset: 16,
521 },
522 Instruction::AddF {
523 target: f(2),
524 lhs: FloatValue::Constant(2.0),
525 rhs: FloatValue::Constant(3.0),
526 },
527 Instruction::SubF {
528 target: f(3),
529 lhs: FloatValue::Constant(2.0),
530 rhs: FloatValue::Constant(3.0),
531 },
532 Instruction::MulF {
533 target: f(4),
534 lhs: FloatValue::Constant(2.0),
535 rhs: FloatValue::Constant(3.0),
536 },
537 Instruction::SafeDivF {
538 target: f(5),
539 lhs: FloatValue::Constant(9.0),
540 rhs: FloatValue::Constant(0.0),
541 },
542 Instruction::TrueModF {
543 target: f(6),
544 lhs: FloatValue::Constant(-7.0),
545 rhs: FloatValue::Constant(3.0),
546 },
547 Instruction::MinF {
548 target: f(7),
549 lhs: FloatValue::Constant(2.0),
550 rhs: FloatValue::Constant(3.0),
551 },
552 Instruction::MaxF {
553 target: f(8),
554 lhs: FloatValue::Constant(2.0),
555 rhs: FloatValue::Constant(3.0),
556 },
557 Instruction::FloorF {
558 target: f(9),
559 value: FloatValue::Constant(2.9),
560 },
561 Instruction::CeilF {
562 target: f(10),
563 value: FloatValue::Constant(2.1),
564 },
565 Instruction::AbsF {
566 target: f(11),
567 value: FloatValue::Constant(-2.0),
568 },
569 Instruction::CmpGtF {
570 target: b(1),
571 lhs: FloatValue::Constant(2.0),
572 rhs: FloatValue::Constant(1.0),
573 },
574 Instruction::CmpGteF {
575 target: b(2),
576 lhs: FloatValue::Constant(2.0),
577 rhs: FloatValue::Constant(2.0),
578 },
579 Instruction::CmpLtF {
580 target: b(3),
581 lhs: FloatValue::Constant(1.0),
582 rhs: FloatValue::Constant(2.0),
583 },
584 Instruction::CmpLteF {
585 target: b(4),
586 lhs: FloatValue::Constant(2.0),
587 rhs: FloatValue::Constant(2.0),
588 },
589 Instruction::CmpEqF {
590 target: b(5),
591 lhs: FloatValue::Constant(2.0),
592 rhs: FloatValue::Constant(2.0),
593 },
594 Instruction::CmpNeF {
595 target: b(6),
596 lhs: FloatValue::Constant(2.0),
597 rhs: FloatValue::Constant(3.0),
598 },
599 Instruction::CmpEqI {
600 target: b(7),
601 lhs: IntValue::Constant(2),
602 rhs: IntValue::Constant(2),
603 },
604 Instruction::CmpNeI {
605 target: b(8),
606 lhs: IntValue::Constant(2),
607 rhs: IntValue::Constant(3),
608 },
609 Instruction::CmpGtI {
610 target: b(9),
611 lhs: IntValue::Constant(3),
612 rhs: IntValue::Constant(2),
613 },
614 Instruction::CmpGteI {
615 target: b(10),
616 lhs: IntValue::Constant(2),
617 rhs: IntValue::Constant(2),
618 },
619 Instruction::CmpLtI {
620 target: b(11),
621 lhs: IntValue::Constant(2),
622 rhs: IntValue::Constant(3),
623 },
624 Instruction::CmpLteI {
625 target: b(12),
626 lhs: IntValue::Constant(2),
627 rhs: IntValue::Constant(2),
628 },
629 Instruction::AndB {
630 target: b(13),
631 lhs: BoolValue::Constant(true),
632 rhs: BoolValue::Constant(true),
633 },
634 Instruction::OrB {
635 target: b(14),
636 lhs: BoolValue::Constant(false),
637 rhs: BoolValue::Constant(true),
638 },
639 Instruction::SetBool {
640 target: b(15),
641 value: true,
642 },
643 Instruction::JumpIf {
644 condition: BoolValue::Constant(false),
645 target: InstructionAddress(34),
646 when_true: true,
647 },
648 Instruction::NotB {
649 target: b(16),
650 value: BoolValue::Constant(false),
651 },
652 Instruction::BoolToFloat {
653 target: f(12),
654 value: BoolValue::Register(b(16)),
655 },
656 Instruction::BoolToInt {
657 target: i(2),
658 value: BoolValue::Register(b(16)),
659 },
660 Instruction::IntToFloat {
661 target: f(13),
662 value: IntValue::Constant(9),
663 },
664 Instruction::IntToBool {
665 target: b(17),
666 value: IntValue::Constant(-1),
667 },
668 Instruction::FloatToBool {
669 target: b(18),
670 value: FloatValue::Constant(-0.0),
671 },
672 Instruction::FloatToInt {
673 target: i(3),
674 value: FloatValue::Constant(-2.9),
675 },
676 Instruction::SelectF {
677 target: f(14),
678 condition: BoolValue::Constant(true),
679 if_true: FloatValue::Constant(4.0),
680 if_false: FloatValue::Constant(5.0),
681 },
682 Instruction::SelectI {
683 target: i(4),
684 condition: BoolValue::Constant(false),
685 if_true: IntValue::Constant(4),
686 if_false: IntValue::Constant(5),
687 },
688 Instruction::SelectB {
689 target: b(19),
690 condition: BoolValue::Constant(true),
691 if_true: BoolValue::Constant(true),
692 if_false: BoolValue::Constant(false),
693 },
694 Instruction::StoreBool {
695 offset: 32,
696 value: BoolValue::Register(b(19)),
697 },
698 Instruction::StoreI32 {
699 offset: 40,
700 value: IntValue::Register(i(3)),
701 },
702 Instruction::StoreF64 {
703 offset: 48,
704 value: FloatValue::Register(f(14)),
705 },
706 Instruction::ReturnCastIf {
707 condition: BoolValue::Constant(false),
708 spell_id: 1,
709 empower_rank: 2,
710 },
711 Instruction::ReturnWaitIf {
712 condition: BoolValue::Constant(false),
713 seconds: 1.0,
714 },
715 Instruction::ReturnUseItemIf {
716 condition: BoolValue::Constant(false),
717 gear_slot: 13,
718 empower_rank: 1,
719 },
720 Instruction::ReturnPoolIf {
721 condition: BoolValue::Constant(true),
722 target: 42.0,
723 },
724 Instruction::ReturnNone,
725 ];
726 let program = test_program(
727 instructions,
728 RegisterCounts {
729 bools: 20,
730 ints: 5,
731 floats: 15,
732 },
733 );
734 let mut buffer = program.schema().new_buffer();
735
736 buffer.bytes_mut()[24..32].copy_from_slice(&(-9_i64).to_ne_bytes());
737 let mut runtime = BytecodeRuntime::new(&program);
738 let result = runtime.execute(&program, &mut buffer, 7.5);
739
740 expect_that!(result, eq(EvalResult::pool(42.0)));
741 expect_that!(
742 bytemuck::pod_read_unaligned::<i32>(&buffer.bytes()[32..36]),
743 eq(1)
744 );
745 expect_that!(
746 bytemuck::pod_read_unaligned::<i32>(&buffer.bytes()[40..44]),
747 eq(-2)
748 );
749 expect_that!(
750 bytemuck::pod_read_unaligned::<f64>(&buffer.bytes()[48..56]),
751 eq(4.0)
752 );
753 }
754
755 #[cfg(feature = "rotation-optimizations")]
756 #[gtest]
757 fn shared_now_register_is_initialized_before_dispatch() {
758 let register = FloatRegister(0);
759 let program = test_program_with_now(
760 vec![
761 Instruction::StoreF64 {
762 offset: 0,
763 value: FloatValue::Register(register),
764 },
765 Instruction::ReturnNone,
766 ],
767 RegisterCounts {
768 bools: 0,
769 ints: 0,
770 floats: 1,
771 },
772 register,
773 );
774 let mut buffer = program.schema().new_buffer();
775 let mut runtime = BytecodeRuntime::new(&program);
776
777 let result = runtime.execute(&program, &mut buffer, 7.5);
778
779 expect_that!(result, eq(EvalResult::NONE));
780 expect_that!(
781 bytemuck::pod_read_unaligned::<f64>(&buffer.bytes()[0..8]),
782 eq(7.5)
783 );
784 }
785
786 #[gtest]
787 fn conversions_define_zero_nan_infinity_and_truncation_exactly() {
788 let instructions = vec![
789 Instruction::FloatToBool {
790 target: BoolRegister(0),
791 value: FloatValue::Constant(0.0),
792 },
793 Instruction::FloatToBool {
794 target: BoolRegister(1),
795 value: FloatValue::Constant(-0.0),
796 },
797 Instruction::FloatToBool {
798 target: BoolRegister(2),
799 value: FloatValue::Constant(f64::NAN),
800 },
801 Instruction::FloatToInt {
802 target: IntRegister(0),
803 value: FloatValue::Constant(f64::NAN),
804 },
805 Instruction::FloatToInt {
806 target: IntRegister(1),
807 value: FloatValue::Constant(f64::NEG_INFINITY),
808 },
809 Instruction::FloatToInt {
810 target: IntRegister(2),
811 value: FloatValue::Constant(f64::INFINITY),
812 },
813 Instruction::FloatToInt {
814 target: IntRegister(3),
815 value: FloatValue::Constant(-9.75),
816 },
817 Instruction::StoreBool {
818 offset: 0,
819 value: BoolValue::Register(BoolRegister(0)),
820 },
821 Instruction::StoreBool {
822 offset: 8,
823 value: BoolValue::Register(BoolRegister(1)),
824 },
825 Instruction::StoreBool {
826 offset: 16,
827 value: BoolValue::Register(BoolRegister(2)),
828 },
829 Instruction::StoreI32 {
830 offset: 24,
831 value: IntValue::Register(IntRegister(0)),
832 },
833 Instruction::StoreI32 {
834 offset: 32,
835 value: IntValue::Register(IntRegister(1)),
836 },
837 Instruction::StoreI32 {
838 offset: 40,
839 value: IntValue::Register(IntRegister(2)),
840 },
841 Instruction::StoreI32 {
842 offset: 48,
843 value: IntValue::Register(IntRegister(3)),
844 },
845 Instruction::ReturnNone,
846 ];
847 let program = test_program(
848 instructions,
849 RegisterCounts {
850 bools: 3,
851 ints: 4,
852 floats: 0,
853 },
854 );
855 let mut buffer = program.schema().new_buffer();
856 let mut runtime = BytecodeRuntime::new(&program);
857
858 expect_that!(
859 runtime.execute(&program, &mut buffer, 0.0).is_none(),
860 is_true()
861 );
862 let read_i32 =
863 |offset| bytemuck::pod_read_unaligned::<i32>(&buffer.bytes()[offset..offset + 4]);
864
865 expect_that!(read_i32(0), eq(0));
866 expect_that!(read_i32(8), eq(0));
867 expect_that!(read_i32(16), eq(1));
868 expect_that!(read_i32(24), eq(0));
869 expect_that!(
870 &runtime.int_registers,
871 elements_are![eq(&0), eq(&i64::MIN), eq(&i64::MAX), eq(&-9)]
872 );
873 }
874
875 #[gtest]
876 fn first_fired_terminator_returns_immediately_and_later_state_is_untouched() {
877 let program = test_program(
878 vec![
879 Instruction::ReturnCastIf {
880 condition: BoolValue::Constant(true),
881 spell_id: 77,
882 empower_rank: 3,
883 },
884 Instruction::StoreI32 {
885 offset: 0,
886 value: IntValue::Constant(99),
887 },
888 Instruction::ReturnUseItemIf {
889 condition: BoolValue::Constant(true),
890 gear_slot: 13,
891 empower_rank: 2,
892 },
893 Instruction::ReturnNone,
894 ],
895 RegisterCounts {
896 bools: 0,
897 ints: 0,
898 floats: 0,
899 },
900 );
901 let mut buffer = program.schema().new_buffer();
902 let mut runtime = BytecodeRuntime::new(&program);
903
904 let result = runtime.execute(&program, &mut buffer, 0.0);
905
906 expect_that!(result, eq(EvalResult::cast(SpellIdx(77), 3)));
907 expect_that!(
908 bytemuck::pod_read_unaligned::<i32>(&buffer.bytes()[0..4]),
909 eq(0)
910 );
911 }
912
913 #[gtest]
914 fn steady_state_dispatch_allocates_nothing() {
915 let program = test_program(
916 vec![Instruction::ReturnWaitIf {
917 condition: BoolValue::Constant(true),
918 seconds: 0.25,
919 }],
920 RegisterCounts {
921 bools: 0,
922 ints: 0,
923 floats: 0,
924 },
925 );
926 let mut buffer = program.schema().new_buffer();
927 let mut runtime = BytecodeRuntime::new(&program);
928
929 let (result, allocations) =
930 crate::test_alloc::allocations_during(|| runtime.execute(&program, &mut buffer, 0.0));
931
932 expect_that!(result, eq(EvalResult::wait(0.25)));
933 expect_that!(allocations, eq(0));
934 }
935}