1use crate::{
3 Context,
4 compiler::RegOp,
5 context::{BadNode, Node},
6 eval::{
7 BulkEvalError, BulkEvaluator, BulkOutput, Function, MathFunction, Tape,
8 Trace, TracingEvalError, TracingEvaluator,
9 },
10 render::{RenderHints, TileSizes},
11 shape::Shape,
12 types::{Grad, Interval},
13 var::VarMap,
14};
15use std::sync::Arc;
16
17mod choice;
18mod data;
19
20pub use choice::Choice;
21use data::BadChoiceSlice;
22pub use data::{VmData, VmWorkspace};
23
24pub type VmFunction = GenericVmFunction<{ u8::MAX as usize }>;
34
35pub type VmShape = Shape<VmFunction>;
37
38#[derive(Default)]
40pub struct EmptyTapeStorage;
41
42#[derive(Clone)]
47pub struct GenericVmTape<const N: usize>(Arc<VmData<N>>);
48
49impl<const N: usize> GenericVmTape<N> {
50 pub fn data(&self) -> &VmData<N> {
52 &self.0
53 }
54}
55
56impl<const N: usize> Tape for GenericVmTape<N> {
57 type Storage = EmptyTapeStorage;
58 fn recycle(self) -> Option<Self::Storage> {
59 Some(EmptyTapeStorage)
60 }
61
62 fn vars(&self) -> &VarMap {
63 &self.0.vars
64 }
65
66 fn output_count(&self) -> usize {
67 self.0.output_count()
68 }
69}
70
71#[derive(Clone, Default, Eq, PartialEq)]
75pub struct VmTrace(Vec<Choice>);
76
77impl VmTrace {
78 pub fn fill(&mut self, v: Choice) {
80 self.0.fill(v);
81 }
82 pub fn resize(&mut self, n: usize, v: Choice) {
84 self.0.resize(n, v);
85 }
86 pub fn as_slice(&self) -> &[Choice] {
88 self.0.as_slice()
89 }
90 pub fn as_mut_slice(&mut self) -> &mut [Choice] {
92 self.0.as_mut_slice()
93 }
94 pub fn as_mut_ptr(&mut self) -> *mut Choice {
96 self.0.as_mut_ptr()
97 }
98}
99
100impl Trace for VmTrace {
101 fn copy_from(&mut self, other: &VmTrace) {
102 self.0.resize(other.0.len(), Choice::Unknown);
103 self.0.copy_from_slice(&other.0);
104 }
105}
106
107#[cfg(any(test, feature = "eval-tests"))]
108impl From<Vec<Choice>> for VmTrace {
109 fn from(v: Vec<Choice>) -> Self {
110 Self(v)
111 }
112}
113
114#[cfg(any(test, feature = "eval-tests"))]
115impl AsRef<[Choice]> for VmTrace {
116 fn as_ref(&self) -> &[Choice] {
117 &self.0
118 }
119}
120
121#[derive(Clone)]
126pub struct GenericVmFunction<const N: usize>(Arc<VmData<N>>);
127
128impl<const N: usize> From<VmData<N>> for GenericVmFunction<N> {
129 fn from(d: VmData<N>) -> Self {
130 Self(d.into())
131 }
132}
133
134impl<const N: usize> GenericVmFunction<N> {
135 pub fn size(&self) -> usize {
137 self.0.len()
138 }
139
140 pub fn recycle(self) -> Option<VmData<N>> {
142 Arc::try_unwrap(self.0).ok()
143 }
144
145 pub fn data(&self) -> &VmData<N> {
147 self.0.as_ref()
148 }
149
150 pub fn tape(&self) -> GenericVmTape<N> {
152 GenericVmTape(self.0.clone())
153 }
154
155 pub fn choice_count(&self) -> usize {
157 self.0.choice_count()
158 }
159
160 pub fn output_count(&self) -> usize {
162 self.0.output_count()
163 }
164
165 pub fn simplify_with<const M: usize>(
167 &self,
168 trace: &VmTrace,
169 storage: VmData<M>,
170 workspace: &mut VmWorkspace<M>,
171 ) -> Result<GenericVmFunction<M>, BadTrace> {
172 let d = self.0.simplify::<M>(trace.as_slice(), workspace, storage)?;
173 Ok(GenericVmFunction(Arc::new(d)))
174 }
175}
176
177#[derive(thiserror::Error, Debug)]
179#[error(transparent)]
180pub struct BadTrace(#[from] pub BadChoiceSlice);
181
182impl<const N: usize> Function for GenericVmFunction<N> {
183 type Storage = VmData<N>;
184 type Workspace = VmWorkspace<N>;
185
186 type TapeStorage = EmptyTapeStorage;
187
188 type FloatSliceEval = VmFloatSliceEval<N>;
189 type GradSliceEval = VmGradSliceEval<N>;
190 type PointEval = VmPointEval<N>;
191 type IntervalEval = VmIntervalEval<N>;
192 type Trace = VmTrace;
193
194 #[inline]
195 fn float_slice_tape(&self, _storage: EmptyTapeStorage) -> GenericVmTape<N> {
196 self.tape()
197 }
198
199 #[inline]
200 fn grad_slice_tape(&self, _storage: EmptyTapeStorage) -> GenericVmTape<N> {
201 self.tape()
202 }
203
204 #[inline]
205 fn point_tape(&self, _storage: EmptyTapeStorage) -> GenericVmTape<N> {
206 self.tape()
207 }
208
209 #[inline]
210 fn interval_tape(&self, _storage: EmptyTapeStorage) -> GenericVmTape<N> {
211 self.tape()
212 }
213
214 #[inline]
215 fn simplify(
216 &self,
217 trace: &Self::Trace,
218 storage: Self::Storage,
219 workspace: &mut Self::Workspace,
220 ) -> Result<Self, BadTrace> {
221 self.simplify_with(trace, storage, workspace)
222 }
223
224 #[inline]
225 fn recycle(self) -> Option<Self::Storage> {
226 GenericVmFunction::recycle(self)
227 }
228
229 #[inline]
230 fn size(&self) -> usize {
231 GenericVmFunction::size(self)
232 }
233
234 #[inline]
235 fn vars(&self) -> &VarMap {
236 &self.0.vars
237 }
238
239 #[inline]
240 fn can_simplify(&self) -> bool {
241 self.0.choice_count() > 0
242 }
243
244 #[inline]
245 fn output_count(&self) -> usize {
246 self.0.output_count()
247 }
248}
249
250impl<const N: usize> RenderHints for GenericVmFunction<N> {
251 fn tile_sizes_3d() -> TileSizes {
252 TileSizes::new(&[128, 64, 32, 16, 8]).unwrap()
253 }
254
255 fn tile_sizes_2d() -> TileSizes {
256 TileSizes::new(&[128, 32, 8]).unwrap()
257 }
258}
259
260impl<const N: usize> MathFunction for GenericVmFunction<N> {
261 fn new(ctx: &Context, nodes: &[Node]) -> Result<Self, BadNode> {
262 let d = VmData::new(ctx, nodes)?;
263 Ok(Self(d.into()))
264 }
265}
266
267struct SlotArray<'a, T>(&'a mut [T]);
271impl<T> std::ops::Index<u8> for SlotArray<'_, T> {
272 type Output = T;
273 fn index(&self, i: u8) -> &Self::Output {
274 &self.0[i as usize]
275 }
276}
277impl<T> std::ops::IndexMut<u8> for SlotArray<'_, T> {
278 fn index_mut(&mut self, i: u8) -> &mut T {
279 &mut self.0[i as usize]
280 }
281}
282impl<T> std::ops::Index<u32> for SlotArray<'_, T> {
283 type Output = T;
284 fn index(&self, i: u32) -> &Self::Output {
285 &self.0[i as usize]
286 }
287}
288impl<T> std::ops::IndexMut<u32> for SlotArray<'_, T> {
289 fn index_mut(&mut self, i: u32) -> &mut T {
290 &mut self.0[i as usize]
291 }
292}
293
294struct TracingVmEval<T> {
298 slots: Vec<T>,
299 out: Vec<T>,
300 choices: VmTrace,
301}
302
303impl<T> Default for TracingVmEval<T> {
304 fn default() -> Self {
305 Self {
306 slots: Vec::default(),
307 out: Vec::default(),
308 choices: VmTrace::default(),
309 }
310 }
311}
312
313impl<T: From<f32> + Clone> TracingVmEval<T> {
314 fn resize_slots<const N: usize>(&mut self, tape: &VmData<N>) {
315 self.slots.resize(tape.slot_count(), f32::NAN.into());
316 self.choices.resize(tape.choice_count(), Choice::Unknown);
317 self.out.resize(tape.output_count(), f32::NAN.into());
318 self.choices.fill(Choice::Unknown);
319 }
320}
321
322#[derive(Default)]
324pub struct VmIntervalEval<const N: usize>(TracingVmEval<Interval>);
325impl<const N: usize> TracingEvaluator for VmIntervalEval<N> {
326 type Data = Interval;
327 type Tape = GenericVmTape<N>;
328 type Trace = VmTrace;
329 type TapeStorage = EmptyTapeStorage;
330
331 #[inline]
332 fn eval(
333 &mut self,
334 tape: &Self::Tape,
335 vars: &[Interval],
336 ) -> Result<(&[Interval], Option<&VmTrace>), TracingEvalError> {
337 tape.vars().check_tracing_arguments(vars)?;
338 let tape = tape.data();
339 self.0.resize_slots(tape);
340
341 let mut simplify = false;
342 let mut v = SlotArray(&mut self.0.slots);
343 let mut choices = self.0.choices.as_mut_slice().iter_mut();
344 for op in tape.iter_asm() {
345 match op {
346 RegOp::Output(arg, i) => {
347 self.0.out[i as usize] = v[arg];
348 }
349 RegOp::Input(out, i) => {
350 v[out] = vars[i as usize];
351 }
352 RegOp::NegReg(out, arg) => {
353 v[out] = -v[arg];
354 }
355 RegOp::AbsReg(out, arg) => {
356 v[out] = v[arg].abs();
357 }
358 RegOp::RecipReg(out, arg) => {
359 v[out] = v[arg].recip();
360 }
361 RegOp::SqrtReg(out, arg) => {
362 v[out] = v[arg].sqrt();
363 }
364 RegOp::SquareReg(out, arg) => {
365 v[out] = v[arg].square();
366 }
367 RegOp::FloorReg(out, arg) => {
368 v[out] = v[arg].floor();
369 }
370 RegOp::CeilReg(out, arg) => {
371 v[out] = v[arg].ceil();
372 }
373 RegOp::RoundReg(out, arg) => {
374 v[out] = v[arg].round();
375 }
376 RegOp::SinReg(out, arg) => {
377 v[out] = v[arg].sin();
378 }
379 RegOp::CosReg(out, arg) => {
380 v[out] = v[arg].cos();
381 }
382 RegOp::TanReg(out, arg) => {
383 v[out] = v[arg].tan();
384 }
385 RegOp::AsinReg(out, arg) => {
386 v[out] = v[arg].asin();
387 }
388 RegOp::AcosReg(out, arg) => {
389 v[out] = v[arg].acos();
390 }
391 RegOp::AtanReg(out, arg) => {
392 v[out] = v[arg].atan();
393 }
394 RegOp::ExpReg(out, arg) => {
395 v[out] = v[arg].exp();
396 }
397 RegOp::LnReg(out, arg) => {
398 v[out] = v[arg].ln();
399 }
400 RegOp::NotReg(out, arg) => {
401 v[out] = if !v[arg].contains(0.0) && !v[arg].has_nan() {
402 Interval::new(0.0, 0.0)
403 } else if v[arg].lower() == 0.0 && v[arg].upper() == 0.0 {
404 Interval::new(1.0, 1.0)
405 } else {
406 Interval::new(0.0, 1.0)
407 };
408 }
409 RegOp::CopyReg(out, arg) => v[out] = v[arg],
410 RegOp::AddRegImm(out, arg, imm) => {
411 v[out] = v[arg] + imm.into();
412 }
413 RegOp::MulRegImm(out, arg, imm) => {
414 v[out] = v[arg] * imm;
415 }
416 RegOp::DivRegImm(out, arg, imm) => {
417 v[out] = v[arg] / imm.into();
418 }
419 RegOp::DivImmReg(out, arg, imm) => {
420 let imm: Interval = imm.into();
421 v[out] = imm / v[arg];
422 }
423 RegOp::AtanRegImm(out, arg, imm) => {
424 v[out] = v[arg].atan2(imm.into());
425 }
426 RegOp::AtanImmReg(out, arg, imm) => {
427 let imm: Interval = imm.into();
428 v[out] = imm.atan2(v[arg]);
429 }
430 RegOp::AtanRegReg(out, lhs, rhs) => {
431 v[out] = v[lhs].atan2(v[rhs]);
432 }
433 RegOp::SubImmReg(out, arg, imm) => {
434 v[out] = Interval::from(imm) - v[arg];
435 }
436 RegOp::SubRegImm(out, arg, imm) => {
437 v[out] = v[arg] - imm.into();
438 }
439 RegOp::MinRegImm(out, arg, imm) => {
440 let (value, choice) = v[arg].min_choice(imm.into());
441 v[out] = value;
442 *choices.next().unwrap() |= choice;
443 simplify |= choice != Choice::Both;
444 }
445 RegOp::MaxRegImm(out, arg, imm) => {
446 let (value, choice) = v[arg].max_choice(imm.into());
447 v[out] = value;
448 *choices.next().unwrap() |= choice;
449 simplify |= choice != Choice::Both;
450 }
451 RegOp::AndRegReg(out, lhs, rhs) => {
452 let (value, choice) = v[lhs].and_choice(v[rhs]);
453 v[out] = value;
454 *choices.next().unwrap() |= choice;
455 simplify |= choice != Choice::Both;
456 }
457 RegOp::AndRegImm(out, arg, imm) => {
458 let (value, choice) = v[arg].and_choice(imm.into());
459 v[out] = value;
460 *choices.next().unwrap() |= choice;
461 simplify |= choice != Choice::Both;
462 }
463 RegOp::OrRegReg(out, lhs, rhs) => {
464 let (value, choice) = v[lhs].or_choice(v[rhs]);
465 v[out] = value;
466 *choices.next().unwrap() |= choice;
467 simplify |= choice != Choice::Both;
468 }
469 RegOp::OrRegImm(out, arg, imm) => {
470 let (value, choice) = v[arg].or_choice(imm.into());
471 v[out] = value;
472 *choices.next().unwrap() |= choice;
473 simplify |= choice != Choice::Both;
474 }
475 RegOp::ModRegReg(out, lhs, rhs) => {
476 v[out] = v[lhs].rem_euclid(v[rhs]);
477 }
478 RegOp::ModRegImm(out, arg, imm) => {
479 v[out] = v[arg].rem_euclid(imm.into());
480 }
481 RegOp::ModImmReg(out, arg, imm) => {
482 v[out] = Interval::from(imm).rem_euclid(v[arg]);
483 }
484 RegOp::AddRegReg(out, lhs, rhs) => v[out] = v[lhs] + v[rhs],
485 RegOp::MulRegReg(out, lhs, rhs) => v[out] = v[lhs] * v[rhs],
486 RegOp::DivRegReg(out, lhs, rhs) => v[out] = v[lhs] / v[rhs],
487 RegOp::SubRegReg(out, lhs, rhs) => v[out] = v[lhs] - v[rhs],
488 RegOp::CompareRegReg(out, lhs, rhs) => {
489 v[out] = if v[lhs].has_nan() || v[rhs].has_nan() {
490 f32::NAN.into()
491 } else if v[lhs].upper() < v[rhs].lower() {
492 Interval::from(-1.0)
493 } else if v[lhs].lower() > v[rhs].upper() {
494 Interval::from(1.0)
495 } else {
496 Interval::new(-1.0, 1.0)
497 };
498 }
499 RegOp::CompareRegImm(out, arg, imm) => {
500 v[out] = if v[arg].has_nan() || imm.is_nan() {
501 f32::NAN.into()
502 } else if v[arg].upper() < imm {
503 Interval::from(-1.0)
504 } else if v[arg].lower() > imm {
505 Interval::from(1.0)
506 } else {
507 Interval::new(-1.0, 1.0)
508 };
509 }
510 RegOp::CompareImmReg(out, arg, imm) => {
511 v[out] = if v[arg].has_nan() || imm.is_nan() {
512 f32::NAN.into()
513 } else if imm < v[arg].lower() {
514 Interval::from(-1.0)
515 } else if imm > v[arg].upper() {
516 Interval::from(1.0)
517 } else {
518 Interval::new(-1.0, 1.0)
519 };
520 }
521 RegOp::MinRegReg(out, lhs, rhs) => {
522 let (value, choice) = v[lhs].min_choice(v[rhs]);
523 v[out] = value;
524 *choices.next().unwrap() |= choice;
525 simplify |= choice != Choice::Both;
526 }
527 RegOp::MaxRegReg(out, lhs, rhs) => {
528 let (value, choice) = v[lhs].max_choice(v[rhs]);
529 v[out] = value;
530 *choices.next().unwrap() |= choice;
531 simplify |= choice != Choice::Both;
532 }
533 RegOp::CopyImm(out, imm) => {
534 v[out] = imm.into();
535 }
536 RegOp::Load(out, mem) => {
537 v[out] = v[mem];
538 }
539 RegOp::Store(out, mem) => {
540 v[mem] = v[out];
541 }
542 }
543 }
544 Ok((
545 &self.0.out,
546 if simplify {
547 Some(&self.0.choices)
548 } else {
549 None
550 },
551 ))
552 }
553}
554
555#[derive(Default)]
557pub struct VmPointEval<const N: usize>(TracingVmEval<f32>);
558impl<const N: usize> TracingEvaluator for VmPointEval<N> {
559 type Data = f32;
560 type Tape = GenericVmTape<N>;
561 type Trace = VmTrace;
562 type TapeStorage = EmptyTapeStorage;
563
564 #[inline]
565 fn eval(
566 &mut self,
567 tape: &Self::Tape,
568 vars: &[f32],
569 ) -> Result<(&[f32], Option<&VmTrace>), TracingEvalError> {
570 tape.vars().check_tracing_arguments(vars)?;
571 let tape = tape.data();
572 self.0.resize_slots(tape);
573
574 let mut choices = self.0.choices.as_mut_slice().iter_mut();
575 let mut simplify = false;
576 let mut v = SlotArray(&mut self.0.slots);
577 for op in tape.iter_asm() {
578 match op {
579 RegOp::Output(arg, i) => {
580 self.0.out[i as usize] = v[arg];
581 }
582 RegOp::Input(out, i) => {
583 v[out] = vars[i as usize];
584 }
585 RegOp::NegReg(out, arg) => {
586 v[out] = -v[arg];
587 }
588 RegOp::AbsReg(out, arg) => {
589 v[out] = v[arg].abs();
590 }
591 RegOp::RecipReg(out, arg) => {
592 v[out] = 1.0 / v[arg];
593 }
594 RegOp::SqrtReg(out, arg) => {
595 v[out] = v[arg].sqrt();
596 }
597 RegOp::SquareReg(out, arg) => {
598 let s = v[arg];
599 v[out] = s * s;
600 }
601 RegOp::FloorReg(out, arg) => {
602 v[out] = v[arg].floor();
603 }
604 RegOp::CeilReg(out, arg) => {
605 v[out] = v[arg].ceil();
606 }
607 RegOp::RoundReg(out, arg) => {
608 v[out] = v[arg].round();
609 }
610 RegOp::SinReg(out, arg) => {
611 v[out] = v[arg].sin();
612 }
613 RegOp::CosReg(out, arg) => {
614 v[out] = v[arg].cos();
615 }
616 RegOp::TanReg(out, arg) => {
617 v[out] = v[arg].tan();
618 }
619 RegOp::AsinReg(out, arg) => {
620 v[out] = v[arg].asin();
621 }
622 RegOp::AcosReg(out, arg) => {
623 v[out] = v[arg].acos();
624 }
625 RegOp::AtanReg(out, arg) => {
626 v[out] = v[arg].atan();
627 }
628 RegOp::ExpReg(out, arg) => {
629 v[out] = v[arg].exp();
630 }
631 RegOp::LnReg(out, arg) => {
632 v[out] = v[arg].ln();
633 }
634 RegOp::NotReg(out, arg) => v[out] = (v[arg] == 0.0).into(),
635 RegOp::CopyReg(out, arg) => {
636 v[out] = v[arg];
637 }
638 RegOp::AddRegImm(out, arg, imm) => {
639 v[out] = v[arg] + imm;
640 }
641 RegOp::MulRegImm(out, arg, imm) => {
642 v[out] = v[arg] * imm;
643 }
644 RegOp::DivRegImm(out, arg, imm) => {
645 v[out] = v[arg] / imm;
646 }
647 RegOp::DivImmReg(out, arg, imm) => {
648 v[out] = imm / v[arg];
649 }
650 RegOp::AtanRegImm(out, arg, imm) => {
651 v[out] = v[arg].atan2(imm);
652 }
653 RegOp::AtanImmReg(out, arg, imm) => {
654 v[out] = imm.atan2(v[arg]);
655 }
656 RegOp::AtanRegReg(out, lhs, rhs) => {
657 v[out] = v[lhs].atan2(v[rhs]);
658 }
659 RegOp::SubImmReg(out, arg, imm) => {
660 v[out] = imm - v[arg];
661 }
662 RegOp::SubRegImm(out, arg, imm) => {
663 v[out] = v[arg] - imm;
664 }
665 RegOp::MinRegImm(out, arg, imm) => {
666 let a = v[arg];
667 let (choice, value) = if a < imm {
668 (Choice::Left, a)
669 } else if imm < a {
670 (Choice::Right, imm)
671 } else {
672 (
673 Choice::Both,
674 if a.is_nan() || imm.is_nan() {
675 f32::NAN
676 } else {
677 imm
678 },
679 )
680 };
681 v[out] = value;
682 *choices.next().unwrap() |= choice;
683 simplify |= choice != Choice::Both;
684 }
685 RegOp::MaxRegImm(out, arg, imm) => {
686 let a = v[arg];
687 let (choice, value) = if a > imm {
688 (Choice::Left, a)
689 } else if imm > a {
690 (Choice::Right, imm)
691 } else {
692 (
693 Choice::Both,
694 if a.is_nan() || imm.is_nan() {
695 f32::NAN
696 } else {
697 imm
698 },
699 )
700 };
701 v[out] = value;
702 *choices.next().unwrap() |= choice;
703 simplify |= choice != Choice::Both;
704 }
705 RegOp::AndRegImm(out, arg, imm) => {
706 let a = v[arg];
707 let (choice, value) = if a == 0.0 {
708 (Choice::Left, a)
709 } else {
710 (Choice::Right, imm)
711 };
712 v[out] = value;
713 *choices.next().unwrap() |= choice;
714 simplify |= choice != Choice::Both;
715 }
716 RegOp::OrRegImm(out, arg, imm) => {
717 let a = v[arg];
718 let (choice, value) = if a != 0.0 {
719 (Choice::Left, a)
720 } else {
721 (Choice::Right, imm)
722 };
723 v[out] = value;
724 *choices.next().unwrap() |= choice;
725 simplify |= choice != Choice::Both;
726 }
727 RegOp::ModRegReg(out, lhs, rhs) => {
728 v[out] = v[lhs].rem_euclid(v[rhs]);
729 }
730 RegOp::ModRegImm(out, arg, imm) => {
731 v[out] = v[arg].rem_euclid(imm);
732 }
733 RegOp::ModImmReg(out, arg, imm) => {
734 v[out] = imm.rem_euclid(v[arg]);
735 }
736 RegOp::AddRegReg(out, lhs, rhs) => {
737 v[out] = v[lhs] + v[rhs];
738 }
739 RegOp::MulRegReg(out, lhs, rhs) => {
740 v[out] = v[lhs] * v[rhs];
741 }
742 RegOp::DivRegReg(out, lhs, rhs) => {
743 v[out] = v[lhs] / v[rhs];
744 }
745 RegOp::CompareRegReg(out, lhs, rhs) => {
746 v[out] = v[lhs]
747 .partial_cmp(&v[rhs])
748 .map(|c| c as i8 as f32)
749 .unwrap_or(f32::NAN)
750 }
751 RegOp::CompareRegImm(out, arg, imm) => {
752 v[out] = v[arg]
753 .partial_cmp(&imm)
754 .map(|c| c as i8 as f32)
755 .unwrap_or(f32::NAN)
756 }
757 RegOp::CompareImmReg(out, arg, imm) => {
758 v[out] = imm
759 .partial_cmp(&v[arg])
760 .map(|c| c as i8 as f32)
761 .unwrap_or(f32::NAN)
762 }
763 RegOp::SubRegReg(out, lhs, rhs) => {
764 v[out] = v[lhs] - v[rhs];
765 }
766 RegOp::MinRegReg(out, lhs, rhs) => {
767 let a = v[lhs];
768 let b = v[rhs];
769 let (choice, value) = if a < b {
770 (Choice::Left, a)
771 } else if b < a {
772 (Choice::Right, b)
773 } else {
774 (
775 Choice::Both,
776 if a.is_nan() || b.is_nan() {
777 f32::NAN
778 } else {
779 b
780 },
781 )
782 };
783 v[out] = value;
784 *choices.next().unwrap() |= choice;
785 simplify |= choice != Choice::Both;
786 }
787 RegOp::MaxRegReg(out, lhs, rhs) => {
788 let a = v[lhs];
789 let b = v[rhs];
790 let (choice, value) = if a > b {
791 (Choice::Left, a)
792 } else if b > a {
793 (Choice::Right, b)
794 } else {
795 (
796 Choice::Both,
797 if a.is_nan() || b.is_nan() {
798 f32::NAN
799 } else {
800 b
801 },
802 )
803 };
804 v[out] = value;
805 *choices.next().unwrap() |= choice;
806 simplify |= choice != Choice::Both;
807 }
808 RegOp::AndRegReg(out, lhs, rhs) => {
809 let a = v[lhs];
810 let b = v[rhs];
811 let (choice, value) = if a == 0.0 {
812 (Choice::Left, a)
813 } else {
814 (Choice::Right, b)
815 };
816 v[out] = value;
817 *choices.next().unwrap() |= choice;
818 simplify |= choice != Choice::Both;
819 }
820 RegOp::OrRegReg(out, lhs, rhs) => {
821 let a = v[lhs];
822 let b = v[rhs];
823 let (choice, value) = if a != 0.0 {
824 (Choice::Left, a)
825 } else {
826 (Choice::Right, b)
827 };
828 v[out] = value;
829 *choices.next().unwrap() |= choice;
830 simplify |= choice != Choice::Both;
831 }
832 RegOp::CopyImm(out, imm) => {
833 v[out] = imm;
834 }
835 RegOp::Load(out, mem) => {
836 v[out] = v[mem];
837 }
838 RegOp::Store(out, mem) => {
839 v[mem] = v[out];
840 }
841 }
842 }
843 Ok((
844 &self.0.out,
845 if simplify {
846 Some(&self.0.choices)
847 } else {
848 None
849 },
850 ))
851 }
852}
853
854#[derive(Default)]
858struct BulkVmEval<T> {
859 slots: Vec<Vec<T>>,
861
862 out: Vec<Vec<T>>,
864}
865
866impl<T: From<f32> + Clone> BulkVmEval<T> {
867 fn resize_slots<const N: usize>(&mut self, tape: &VmData<N>, size: usize) {
869 self.slots
870 .resize_with(tape.slot_count(), || vec![f32::NAN.into(); size]);
871 for s in self.slots.iter_mut() {
872 s.resize(size, f32::NAN.into());
873 }
874
875 self.out
876 .resize_with(tape.output_count(), || vec![f32::NAN.into(); size]);
877 for o in self.out.iter_mut() {
878 o.resize(size, f32::NAN.into());
879 }
880 }
881}
882
883#[derive(Default)]
885pub struct VmFloatSliceEval<const N: usize>(BulkVmEval<f32>);
886impl<const N: usize> BulkEvaluator for VmFloatSliceEval<N> {
887 type Data = f32;
888 type Tape = GenericVmTape<N>;
889 type TapeStorage = EmptyTapeStorage;
890
891 #[inline]
892 fn eval<V: std::ops::Deref<Target = [Self::Data]>>(
893 &mut self,
894 tape: &Self::Tape,
895 vars: &[V],
896 ) -> Result<BulkOutput<'_, f32>, BulkEvalError> {
897 tape.vars().check_bulk_arguments(vars)?;
898 let tape = tape.data();
899
900 let size = vars.first().map(|v| v.len()).unwrap_or(0);
901 self.0.resize_slots(tape, size);
902
903 let mut v = SlotArray(&mut self.0.slots);
904 for op in tape.iter_asm() {
905 match op {
906 RegOp::Output(arg, i) => {
907 self.0.out[i as usize][0..size]
908 .copy_from_slice(&v[arg][0..size]);
909 }
910 RegOp::Input(out, i) => {
911 v[out][0..size].copy_from_slice(&vars[i as usize]);
912 }
913 RegOp::NegReg(out, arg) => {
914 for i in 0..size {
915 v[out][i] = -v[arg][i];
916 }
917 }
918 RegOp::AbsReg(out, arg) => {
919 for i in 0..size {
920 v[out][i] = v[arg][i].abs();
921 }
922 }
923 RegOp::RecipReg(out, arg) => {
924 for i in 0..size {
925 v[out][i] = 1.0 / v[arg][i];
926 }
927 }
928 RegOp::SqrtReg(out, arg) => {
929 for i in 0..size {
930 v[out][i] = v[arg][i].sqrt();
931 }
932 }
933 RegOp::SquareReg(out, arg) => {
934 for i in 0..size {
935 let s = v[arg][i];
936 v[out][i] = s * s;
937 }
938 }
939 RegOp::FloorReg(out, arg) => {
940 for i in 0..size {
941 v[out][i] = v[arg][i].floor();
942 }
943 }
944 RegOp::CeilReg(out, arg) => {
945 for i in 0..size {
946 v[out][i] = v[arg][i].ceil();
947 }
948 }
949 RegOp::RoundReg(out, arg) => {
950 for i in 0..size {
951 v[out][i] = v[arg][i].round();
952 }
953 }
954 RegOp::SinReg(out, arg) => {
955 for i in 0..size {
956 v[out][i] = v[arg][i].sin();
957 }
958 }
959 RegOp::CosReg(out, arg) => {
960 for i in 0..size {
961 v[out][i] = v[arg][i].cos();
962 }
963 }
964 RegOp::TanReg(out, arg) => {
965 for i in 0..size {
966 v[out][i] = v[arg][i].tan();
967 }
968 }
969 RegOp::AsinReg(out, arg) => {
970 for i in 0..size {
971 v[out][i] = v[arg][i].asin();
972 }
973 }
974 RegOp::AcosReg(out, arg) => {
975 for i in 0..size {
976 v[out][i] = v[arg][i].acos();
977 }
978 }
979 RegOp::AtanReg(out, arg) => {
980 for i in 0..size {
981 v[out][i] = v[arg][i].atan();
982 }
983 }
984 RegOp::ExpReg(out, arg) => {
985 for i in 0..size {
986 v[out][i] = v[arg][i].exp();
987 }
988 }
989 RegOp::LnReg(out, arg) => {
990 for i in 0..size {
991 v[out][i] = v[arg][i].ln();
992 }
993 }
994 RegOp::NotReg(out, arg) => {
995 for i in 0..size {
996 v[out][i] = (v[arg][i] == 0.0).into();
997 }
998 }
999 RegOp::CopyReg(out, arg) => {
1000 for i in 0..size {
1001 v[out][i] = v[arg][i];
1002 }
1003 }
1004 RegOp::AddRegImm(out, arg, imm) => {
1005 for i in 0..size {
1006 v[out][i] = v[arg][i] + imm;
1007 }
1008 }
1009 RegOp::MulRegImm(out, arg, imm) => {
1010 for i in 0..size {
1011 v[out][i] = v[arg][i] * imm;
1012 }
1013 }
1014 RegOp::DivRegImm(out, arg, imm) => {
1015 for i in 0..size {
1016 v[out][i] = v[arg][i] / imm;
1017 }
1018 }
1019 RegOp::DivImmReg(out, arg, imm) => {
1020 for i in 0..size {
1021 v[out][i] = imm / v[arg][i];
1022 }
1023 }
1024 RegOp::AtanRegImm(out, arg, imm) => {
1025 for i in 0..size {
1026 v[out][i] = v[arg][i].atan2(imm);
1027 }
1028 }
1029 RegOp::AtanImmReg(out, arg, imm) => {
1030 for i in 0..size {
1031 v[out][i] = imm.atan2(v[arg][i]);
1032 }
1033 }
1034 RegOp::AtanRegReg(out, lhs, rhs) => {
1035 for i in 0..size {
1036 v[out][i] = v[lhs][i].atan2(v[rhs][i]);
1037 }
1038 }
1039 RegOp::SubImmReg(out, arg, imm) => {
1040 for i in 0..size {
1041 v[out][i] = imm - v[arg][i];
1042 }
1043 }
1044 RegOp::SubRegImm(out, arg, imm) => {
1045 for i in 0..size {
1046 v[out][i] = v[arg][i] - imm;
1047 }
1048 }
1049 RegOp::CompareImmReg(out, arg, imm) => {
1050 for i in 0..size {
1051 v[out][i] = imm
1052 .partial_cmp(&v[arg][i])
1053 .map(|c| c as i8 as f32)
1054 .unwrap_or(f32::NAN)
1055 }
1056 }
1057 RegOp::CompareRegImm(out, arg, imm) => {
1058 for i in 0..size {
1059 v[out][i] = v[arg][i]
1060 .partial_cmp(&imm)
1061 .map(|c| c as i8 as f32)
1062 .unwrap_or(f32::NAN)
1063 }
1064 }
1065 RegOp::MinRegImm(out, arg, imm) => {
1066 for i in 0..size {
1067 v[out][i] = if v[arg][i].is_nan() || imm.is_nan() {
1068 f32::NAN
1069 } else {
1070 v[arg][i].min(imm)
1071 };
1072 }
1073 }
1074 RegOp::MaxRegImm(out, arg, imm) => {
1075 for i in 0..size {
1076 v[out][i] = if v[arg][i].is_nan() || imm.is_nan() {
1077 f32::NAN
1078 } else {
1079 v[arg][i].max(imm)
1080 };
1081 }
1082 }
1083 RegOp::AndRegImm(out, arg, imm) => {
1084 for i in 0..size {
1085 v[out][i] =
1086 if v[arg][i] == 0.0 { v[arg][i] } else { imm };
1087 }
1088 }
1089 RegOp::OrRegImm(out, arg, imm) => {
1090 for i in 0..size {
1091 v[out][i] =
1092 if v[arg][i] != 0.0 { v[arg][i] } else { imm };
1093 }
1094 }
1095 RegOp::ModRegReg(out, lhs, rhs) => {
1096 for i in 0..size {
1097 v[out][i] = v[lhs][i].rem_euclid(v[rhs][i]);
1098 }
1099 }
1100 RegOp::ModRegImm(out, arg, imm) => {
1101 for i in 0..size {
1102 v[out][i] = v[arg][i].rem_euclid(imm);
1103 }
1104 }
1105 RegOp::ModImmReg(out, arg, imm) => {
1106 for i in 0..size {
1107 v[out][i] = imm.rem_euclid(v[arg][i]);
1108 }
1109 }
1110 RegOp::AddRegReg(out, lhs, rhs) => {
1111 for i in 0..size {
1112 v[out][i] = v[lhs][i] + v[rhs][i];
1113 }
1114 }
1115 RegOp::MulRegReg(out, lhs, rhs) => {
1116 for i in 0..size {
1117 v[out][i] = v[lhs][i] * v[rhs][i];
1118 }
1119 }
1120 RegOp::DivRegReg(out, lhs, rhs) => {
1121 for i in 0..size {
1122 v[out][i] = v[lhs][i] / v[rhs][i];
1123 }
1124 }
1125 RegOp::SubRegReg(out, lhs, rhs) => {
1126 for i in 0..size {
1127 v[out][i] = v[lhs][i] - v[rhs][i];
1128 }
1129 }
1130 RegOp::CompareRegReg(out, lhs, rhs) => {
1131 for i in 0..size {
1132 v[out][i] = v[lhs][i]
1133 .partial_cmp(&v[rhs][i])
1134 .map(|c| c as i8 as f32)
1135 .unwrap_or(f32::NAN)
1136 }
1137 }
1138 RegOp::MinRegReg(out, lhs, rhs) => {
1139 for i in 0..size {
1140 v[out][i] = if v[lhs][i].is_nan() || v[rhs][i].is_nan()
1141 {
1142 f32::NAN
1143 } else {
1144 v[lhs][i].min(v[rhs][i])
1145 };
1146 }
1147 }
1148 RegOp::MaxRegReg(out, lhs, rhs) => {
1149 for i in 0..size {
1150 v[out][i] = if v[lhs][i].is_nan() || v[rhs][i].is_nan()
1151 {
1152 f32::NAN
1153 } else {
1154 v[lhs][i].max(v[rhs][i])
1155 };
1156 }
1157 }
1158 RegOp::AndRegReg(out, lhs, rhs) => {
1159 for i in 0..size {
1160 v[out][i] = if v[lhs][i] == 0.0 {
1161 v[lhs][i]
1162 } else {
1163 v[rhs][i]
1164 };
1165 }
1166 }
1167 RegOp::OrRegReg(out, lhs, rhs) => {
1168 for i in 0..size {
1169 v[out][i] = if v[lhs][i] != 0.0 {
1170 v[lhs][i]
1171 } else {
1172 v[rhs][i]
1173 };
1174 }
1175 }
1176 RegOp::CopyImm(out, imm) => {
1177 for i in 0..size {
1178 v[out][i] = imm;
1179 }
1180 }
1181 RegOp::Load(out, mem) => {
1182 for i in 0..size {
1183 v[out][i] = v[mem][i];
1184 }
1185 }
1186 RegOp::Store(out, mem) => {
1187 for i in 0..size {
1188 v[mem][i] = v[out][i];
1189 }
1190 }
1191 }
1192 }
1193 Ok(BulkOutput::new(&self.0.out, size))
1194 }
1195}
1196
1197#[derive(Default)]
1199pub struct VmGradSliceEval<const N: usize>(BulkVmEval<Grad>);
1200impl<const N: usize> BulkEvaluator for VmGradSliceEval<N> {
1201 type Data = Grad;
1202 type Tape = GenericVmTape<N>;
1203 type TapeStorage = EmptyTapeStorage;
1204
1205 #[inline]
1206 fn eval<V: std::ops::Deref<Target = [Self::Data]>>(
1207 &mut self,
1208 tape: &Self::Tape,
1209 vars: &[V],
1210 ) -> Result<BulkOutput<'_, Grad>, BulkEvalError> {
1211 tape.vars().check_bulk_arguments(vars)?;
1212 let tape = tape.data();
1213 let size = vars.first().map(|v| v.len()).unwrap_or(0);
1214 self.0.resize_slots(tape, size);
1215
1216 let mut v = SlotArray(&mut self.0.slots);
1217 for op in tape.iter_asm() {
1218 match op {
1219 RegOp::Output(arg, i) => {
1220 self.0.out[i as usize][0..size]
1221 .copy_from_slice(&v[arg][0..size]);
1222 }
1223 RegOp::Input(out, i) => {
1224 v[out][0..size].copy_from_slice(&vars[i as usize]);
1225 }
1226 RegOp::NegReg(out, arg) => {
1227 for i in 0..size {
1228 v[out][i] = -v[arg][i];
1229 }
1230 }
1231 RegOp::AbsReg(out, arg) => {
1232 for i in 0..size {
1233 v[out][i] = v[arg][i].abs();
1234 }
1235 }
1236 RegOp::RecipReg(out, arg) => {
1237 let one: Grad = 1.0.into();
1238 for i in 0..size {
1239 v[out][i] = one / v[arg][i];
1240 }
1241 }
1242 RegOp::SqrtReg(out, arg) => {
1243 for i in 0..size {
1244 v[out][i] = v[arg][i].sqrt();
1245 }
1246 }
1247 RegOp::SquareReg(out, arg) => {
1248 for i in 0..size {
1249 let s = v[arg][i];
1250 v[out][i] = s * s;
1251 }
1252 }
1253 RegOp::FloorReg(out, arg) => {
1254 for i in 0..size {
1255 v[out][i] = v[arg][i].floor();
1256 }
1257 }
1258 RegOp::CeilReg(out, arg) => {
1259 for i in 0..size {
1260 v[out][i] = v[arg][i].ceil();
1261 }
1262 }
1263 RegOp::RoundReg(out, arg) => {
1264 for i in 0..size {
1265 v[out][i] = v[arg][i].round();
1266 }
1267 }
1268 RegOp::SinReg(out, arg) => {
1269 for i in 0..size {
1270 v[out][i] = v[arg][i].sin();
1271 }
1272 }
1273 RegOp::CosReg(out, arg) => {
1274 for i in 0..size {
1275 v[out][i] = v[arg][i].cos();
1276 }
1277 }
1278 RegOp::TanReg(out, arg) => {
1279 for i in 0..size {
1280 v[out][i] = v[arg][i].tan();
1281 }
1282 }
1283 RegOp::AsinReg(out, arg) => {
1284 for i in 0..size {
1285 v[out][i] = v[arg][i].asin();
1286 }
1287 }
1288 RegOp::AcosReg(out, arg) => {
1289 for i in 0..size {
1290 v[out][i] = v[arg][i].acos();
1291 }
1292 }
1293 RegOp::AtanReg(out, arg) => {
1294 for i in 0..size {
1295 v[out][i] = v[arg][i].atan();
1296 }
1297 }
1298 RegOp::ExpReg(out, arg) => {
1299 for i in 0..size {
1300 v[out][i] = v[arg][i].exp();
1301 }
1302 }
1303 RegOp::LnReg(out, arg) => {
1304 for i in 0..size {
1305 v[out][i] = v[arg][i].ln();
1306 }
1307 }
1308 RegOp::NotReg(out, arg) => {
1309 for i in 0..size {
1310 v[out][i] = f32::from(v[arg][i].v == 0.0).into();
1311 }
1312 }
1313 RegOp::CopyReg(out, arg) => {
1314 for i in 0..size {
1315 v[out][i] = v[arg][i];
1316 }
1317 }
1318 RegOp::AddRegImm(out, arg, imm) => {
1319 for i in 0..size {
1320 v[out][i] = v[arg][i] + imm.into();
1321 }
1322 }
1323 RegOp::MulRegImm(out, arg, imm) => {
1324 for i in 0..size {
1325 v[out][i] = v[arg][i] * imm;
1326 }
1327 }
1328 RegOp::DivRegImm(out, arg, imm) => {
1329 for i in 0..size {
1330 v[out][i] = v[arg][i] / imm.into();
1331 }
1332 }
1333 RegOp::DivImmReg(out, arg, imm) => {
1334 let imm = Grad::from(imm);
1335 for i in 0..size {
1336 v[out][i] = imm / v[arg][i];
1337 }
1338 }
1339 RegOp::AtanRegImm(out, arg, imm) => {
1340 let imm = Grad::from(imm);
1341 for i in 0..size {
1342 v[out][i] = v[arg][i].atan2(imm);
1343 }
1344 }
1345 RegOp::AtanImmReg(out, arg, imm) => {
1346 let imm = Grad::from(imm);
1347 for i in 0..size {
1348 v[out][i] = imm.atan2(v[arg][i]);
1349 }
1350 }
1351 RegOp::AtanRegReg(out, lhs, rhs) => {
1352 for i in 0..size {
1353 v[out][i] = v[lhs][i].atan2(v[rhs][i]);
1354 }
1355 }
1356 RegOp::SubImmReg(out, arg, imm) => {
1357 let imm: Grad = imm.into();
1358 for i in 0..size {
1359 v[out][i] = imm - v[arg][i];
1360 }
1361 }
1362 RegOp::SubRegImm(out, arg, imm) => {
1363 let imm: Grad = imm.into();
1364 for i in 0..size {
1365 v[out][i] = v[arg][i] - imm;
1366 }
1367 }
1368 RegOp::CompareImmReg(out, arg, imm) => {
1369 for i in 0..size {
1370 let p = imm
1371 .partial_cmp(&v[arg][i].v)
1372 .map(|c| c as i8 as f32)
1373 .unwrap_or(f32::NAN);
1374 v[out][i] = Grad::new(p, 0.0, 0.0, 0.0);
1375 }
1376 }
1377 RegOp::CompareRegImm(out, arg, imm) => {
1378 for i in 0..size {
1379 let p = v[arg][i]
1380 .v
1381 .partial_cmp(&imm)
1382 .map(|c| c as i8 as f32)
1383 .unwrap_or(f32::NAN);
1384 v[out][i] = Grad::new(p, 0.0, 0.0, 0.0);
1385 }
1386 }
1387 RegOp::MinRegImm(out, arg, imm) => {
1388 let imm: Grad = imm.into();
1389 for i in 0..size {
1390 v[out][i] = if v[arg][i].v.is_nan() || imm.v.is_nan() {
1391 f32::NAN.into()
1392 } else {
1393 v[arg][i].min(imm)
1394 };
1395 }
1396 }
1397 RegOp::MaxRegImm(out, arg, imm) => {
1398 let imm: Grad = imm.into();
1399 for i in 0..size {
1400 v[out][i] = if v[arg][i].v.is_nan() || imm.v.is_nan() {
1401 f32::NAN.into()
1402 } else {
1403 v[arg][i].max(imm)
1404 };
1405 }
1406 }
1407 RegOp::ModRegReg(out, lhs, rhs) => {
1408 for i in 0..size {
1409 v[out][i] = v[lhs][i].rem_euclid(v[rhs][i]);
1410 }
1411 }
1412 RegOp::ModRegImm(out, arg, imm) => {
1413 for i in 0..size {
1414 v[out][i] = v[arg][i].rem_euclid(imm.into());
1415 }
1416 }
1417 RegOp::ModImmReg(out, arg, imm) => {
1418 for i in 0..size {
1419 v[out][i] = Grad::from(imm).rem_euclid(v[arg][i]);
1420 }
1421 }
1422 RegOp::AddRegReg(out, lhs, rhs) => {
1423 for i in 0..size {
1424 v[out][i] = v[lhs][i] + v[rhs][i];
1425 }
1426 }
1427 RegOp::MulRegReg(out, lhs, rhs) => {
1428 for i in 0..size {
1429 v[out][i] = v[lhs][i] * v[rhs][i];
1430 }
1431 }
1432 RegOp::AndRegReg(out, lhs, rhs) => {
1433 for i in 0..size {
1434 v[out][i] = if v[lhs][i].v == 0.0 {
1435 v[lhs][i]
1436 } else {
1437 v[rhs][i]
1438 };
1439 }
1440 }
1441 RegOp::AndRegImm(out, arg, imm) => {
1442 for i in 0..size {
1443 v[out][i] = if v[arg][i].v == 0.0 {
1444 v[arg][i]
1445 } else {
1446 imm.into()
1447 };
1448 }
1449 }
1450 RegOp::OrRegReg(out, lhs, rhs) => {
1451 for i in 0..size {
1452 v[out][i] = if v[lhs][i].v != 0.0 {
1453 v[lhs][i]
1454 } else {
1455 v[rhs][i]
1456 };
1457 }
1458 }
1459 RegOp::OrRegImm(out, arg, imm) => {
1460 for i in 0..size {
1461 v[out][i] = if v[arg][i].v != 0.0 {
1462 v[arg][i]
1463 } else {
1464 imm.into()
1465 };
1466 }
1467 }
1468 RegOp::DivRegReg(out, lhs, rhs) => {
1469 for i in 0..size {
1470 v[out][i] = v[lhs][i] / v[rhs][i];
1471 }
1472 }
1473 RegOp::SubRegReg(out, lhs, rhs) => {
1474 for i in 0..size {
1475 v[out][i] = v[lhs][i] - v[rhs][i];
1476 }
1477 }
1478 RegOp::CompareRegReg(out, lhs, rhs) => {
1479 for i in 0..size {
1480 let p = v[lhs][i]
1481 .v
1482 .partial_cmp(&v[rhs][i].v)
1483 .map(|c| c as i8 as f32)
1484 .unwrap_or(f32::NAN);
1485 v[out][i] = Grad::new(p, 0.0, 0.0, 0.0);
1486 }
1487 }
1488 RegOp::MinRegReg(out, lhs, rhs) => {
1489 for i in 0..size {
1490 v[out][i] =
1491 if v[lhs][i].v.is_nan() || v[rhs][i].v.is_nan() {
1492 f32::NAN.into()
1493 } else {
1494 v[lhs][i].min(v[rhs][i])
1495 };
1496 }
1497 }
1498 RegOp::MaxRegReg(out, lhs, rhs) => {
1499 for i in 0..size {
1500 v[out][i] =
1501 if v[lhs][i].v.is_nan() || v[rhs][i].v.is_nan() {
1502 f32::NAN.into()
1503 } else {
1504 v[lhs][i].max(v[rhs][i])
1505 };
1506 }
1507 }
1508 RegOp::CopyImm(out, imm) => {
1509 let imm: Grad = imm.into();
1510 for i in 0..size {
1511 v[out][i] = imm;
1512 }
1513 }
1514 RegOp::Load(out, mem) => {
1515 for i in 0..size {
1516 v[out][i] = v[mem][i];
1517 }
1518 }
1519 RegOp::Store(out, mem) => {
1520 for i in 0..size {
1521 v[mem][i] = v[out][i];
1522 }
1523 }
1524 }
1525 }
1526 Ok(BulkOutput::new(&self.0.out, size))
1527 }
1528}
1529
1530#[cfg(test)]
1531mod test {
1532 use super::*;
1533 crate::grad_slice_tests!(VmFunction);
1534 crate::interval_tests!(VmFunction);
1535 crate::float_slice_tests!(VmFunction);
1536 crate::point_tests!(VmFunction);
1537}