1use crate::mmap::{Mmap, MmapWriter};
27use fidget_core::{
28 compiler::RegOp,
29 context::{BadNode, Context, Node},
30 eval::{
31 BulkEvalError, BulkEvaluator, BulkOutput, Function, MathFunction, Tape,
32 TracingEvalError, TracingEvaluator,
33 },
34 render::{RenderHints, TileSizes},
35 types::{Grad, Interval},
36 var::VarMap,
37 vm::{BadTrace, Choice, GenericVmFunction, VmData, VmTrace, VmWorkspace},
38};
39
40use dynasmrt::{
41 AssemblyOffset, DynamicLabel, DynasmApi, DynasmError, DynasmLabelApi,
42 TargetKind, components::PatchLoc, dynasm,
43};
44use std::sync::Arc;
45
46mod mmap;
47mod permit;
48pub(crate) use permit::WritePermit;
49
50mod float_slice;
52mod grad_slice;
53mod interval;
54mod point;
55
56#[cfg(not(any(
57 target_os = "linux",
58 target_os = "macos",
59 target_os = "windows"
60)))]
61compile_error!(
62 "The `jit` module only builds on Linux, macOS, and Windows; \
63 please disable the `jit` feature"
64);
65
66#[cfg(target_arch = "aarch64")]
67mod aarch64;
68#[cfg(target_arch = "aarch64")]
69use aarch64 as arch;
70
71#[cfg(target_arch = "x86_64")]
72mod x86_64;
73#[cfg(target_arch = "x86_64")]
74use x86_64 as arch;
75
76const REGISTER_LIMIT: usize = arch::REGISTER_LIMIT;
78
79const OFFSET: u8 = arch::OFFSET;
81
82const IMM_REG: u8 = arch::IMM_REG;
88
89fn reg(r: u8) -> u8 {
98 let out = r.wrapping_add(OFFSET);
99 assert!(out < 32);
100 out
101}
102
103const CHOICE_LEFT: u32 = Choice::Left as u32;
104const CHOICE_RIGHT: u32 = Choice::Right as u32;
105const CHOICE_BOTH: u32 = Choice::Both as u32;
106
107trait Assembler {
109 type Data;
113
114 fn init(m: Mmap, slot_count: usize) -> Self;
119
120 fn bytes_per_clause() -> usize {
122 8 }
124
125 fn build_load(&mut self, dst_reg: u8, src_mem: u32);
127
128 fn build_store(&mut self, dst_mem: u32, src_reg: u8);
130
131 fn build_input(&mut self, out_reg: u8, src_arg: u32);
133
134 fn build_output(&mut self, arg_reg: u8, out_index: u32);
136
137 fn build_copy(&mut self, out_reg: u8, lhs_reg: u8);
139
140 fn build_neg(&mut self, out_reg: u8, lhs_reg: u8);
142
143 fn build_abs(&mut self, out_reg: u8, lhs_reg: u8);
145
146 fn build_recip(&mut self, out_reg: u8, lhs_reg: u8);
148
149 fn build_sqrt(&mut self, out_reg: u8, lhs_reg: u8);
151
152 fn build_sin(&mut self, out_reg: u8, lhs_reg: u8);
154
155 fn build_cos(&mut self, out_reg: u8, lhs_reg: u8);
157
158 fn build_tan(&mut self, out_reg: u8, lhs_reg: u8);
160
161 fn build_asin(&mut self, out_reg: u8, lhs_reg: u8);
163
164 fn build_acos(&mut self, out_reg: u8, lhs_reg: u8);
166
167 fn build_atan(&mut self, out_reg: u8, lhs_reg: u8);
169
170 fn build_exp(&mut self, out_reg: u8, lhs_reg: u8);
172
173 fn build_ln(&mut self, out_reg: u8, lhs_reg: u8);
175
176 fn build_compare(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
178
179 fn build_square(&mut self, out_reg: u8, lhs_reg: u8) {
185 self.build_mul(out_reg, lhs_reg, lhs_reg)
186 }
187
188 fn build_floor(&mut self, out_reg: u8, lhs_reg: u8);
190
191 fn build_ceil(&mut self, out_reg: u8, lhs_reg: u8);
193
194 fn build_round(&mut self, out_reg: u8, lhs_reg: u8);
196
197 fn build_not(&mut self, out_reg: u8, lhs_reg: u8);
199
200 fn build_and(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
202
203 fn build_or(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
205
206 fn build_add(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
208
209 fn build_sub(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
211
212 fn build_mul(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
214
215 fn build_div(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
217
218 fn build_atan2(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
220
221 fn build_max(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
226
227 fn build_min(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
232
233 fn build_mod(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
235
236 fn build_add_imm(&mut self, out_reg: u8, lhs_reg: u8, imm: f32) {
244 let imm = self.load_imm(imm);
245 self.build_add(out_reg, lhs_reg, imm);
246 }
247 fn build_sub_imm_reg(&mut self, out_reg: u8, arg: u8, imm: f32) {
251 let imm = self.load_imm(imm);
252 self.build_sub(out_reg, imm, arg);
253 }
254 fn build_sub_reg_imm(&mut self, out_reg: u8, arg: u8, imm: f32) {
258 let imm = self.load_imm(imm);
259 self.build_sub(out_reg, arg, imm);
260 }
261 fn build_mul_imm(&mut self, out_reg: u8, lhs_reg: u8, imm: f32) {
265 let imm = self.load_imm(imm);
266 self.build_mul(out_reg, lhs_reg, imm);
267 }
268
269 fn load_imm(&mut self, imm: f32) -> u8;
271
272 fn finalize(self) -> Result<Mmap, DynasmError>;
274}
275
276pub trait SimdSize {
278 const SIMD_SIZE: usize;
283}
284
285pub(crate) struct AssemblerData<T> {
288 ops: MmapAssembler,
289
290 mem_offset: usize,
292
293 saved_callee_regs: bool,
298
299 _p: std::marker::PhantomData<*const T>,
300}
301
302impl<T> AssemblerData<T> {
303 fn new(mmap: Mmap) -> Self {
304 Self {
305 ops: MmapAssembler::from(mmap),
306 mem_offset: 0,
307 saved_callee_regs: false,
308 _p: std::marker::PhantomData,
309 }
310 }
311
312 fn prepare_stack(&mut self, slot_count: usize, stack_size: usize) {
313 let mem = slot_count.saturating_sub(REGISTER_LIMIT)
315 * std::mem::size_of::<T>()
316 + stack_size;
317
318 self.mem_offset = mem.next_multiple_of(16);
320 self.push_stack();
321 }
322
323 fn stack_pos(&self, slot: u32) -> u32 {
324 assert!(slot >= REGISTER_LIMIT as u32);
325 (slot - REGISTER_LIMIT as u32) * std::mem::size_of::<T>() as u32
326 }
327}
328
329#[cfg(target_arch = "x86_64")]
330impl<T> AssemblerData<T> {
331 fn push_stack(&mut self) {
332 dynasm!(self.ops
333 ; sub rsp, self.mem_offset as i32
334 );
335 }
336
337 fn finalize(mut self) -> Result<Mmap, DynasmError> {
338 dynasm!(self.ops
339 ; add rsp, self.mem_offset as i32
340 ; pop rbp
341 ; vzeroupper
342 ; ret
343 );
344 self.ops.finalize()
345 }
346}
347
348#[cfg(target_arch = "aarch64")]
349#[allow(clippy::unnecessary_cast)] impl<T> AssemblerData<T> {
351 fn push_stack(&mut self) {
352 if self.mem_offset < 4096 {
353 dynasm!(self.ops
354 ; sub sp, sp, self.mem_offset as u32
355 );
356 } else if self.mem_offset < 65536 {
357 dynasm!(self.ops
358 ; mov w28, self.mem_offset as u32
359 ; sub sp, sp, w28
360 );
361 } else {
362 panic!("invalid mem offset: {} is too large", self.mem_offset);
363 }
364 }
365
366 fn finalize(mut self) -> Result<Mmap, DynasmError> {
367 if self.mem_offset < 4096 {
369 dynasm!(self.ops
370 ; add sp, sp, self.mem_offset as u32
371 );
372 } else if self.mem_offset < 65536 {
373 dynasm!(self.ops
374 ; mov w9, self.mem_offset as u32
375 ; add sp, sp, w9
376 );
377 } else {
378 panic!("invalid mem offset: {}", self.mem_offset);
379 }
380
381 dynasm!(self.ops
382 ; ret
383 );
384 self.ops.finalize()
385 }
386}
387
388#[cfg(target_arch = "x86_64")]
391type Relocation = dynasmrt::x64::X64Relocation;
392
393#[cfg(target_arch = "aarch64")]
394type Relocation = dynasmrt::aarch64::Aarch64Relocation;
395
396struct MmapAssembler {
397 mmap: MmapWriter,
398
399 global_labels: [Option<AssemblyOffset>; 26],
400 local_labels: [Option<AssemblyOffset>; 26],
401
402 global_relocs: arrayvec::ArrayVec<(PatchLoc<Relocation>, u8), 2>,
403 local_relocs: arrayvec::ArrayVec<(PatchLoc<Relocation>, u8), 8>,
404}
405
406impl Extend<u8> for MmapAssembler {
407 fn extend<T>(&mut self, iter: T)
408 where
409 T: IntoIterator<Item = u8>,
410 {
411 for c in iter.into_iter() {
412 self.push(c);
413 }
414 }
415}
416
417impl<'a> Extend<&'a u8> for MmapAssembler {
418 fn extend<T>(&mut self, iter: T)
419 where
420 T: IntoIterator<Item = &'a u8>,
421 {
422 for c in iter.into_iter() {
423 self.push(*c);
424 }
425 }
426}
427
428impl DynasmApi for MmapAssembler {
429 #[inline(always)]
430 fn offset(&self) -> AssemblyOffset {
431 AssemblyOffset(self.mmap.len())
432 }
433
434 #[inline(always)]
435 fn push(&mut self, byte: u8) {
436 self.mmap.push(byte);
437 }
438
439 #[inline(always)]
440 fn align(&mut self, alignment: usize, with: u8) {
441 let offset = self.offset().0 % alignment;
442 if offset != 0 {
443 for _ in offset..alignment {
444 self.push(with);
445 }
446 }
447 }
448
449 #[inline(always)]
450 fn push_u32(&mut self, value: u32) {
451 for b in value.to_le_bytes() {
452 self.mmap.push(b);
453 }
454 }
455}
456
457impl DynasmLabelApi for MmapAssembler {
472 type Relocation = Relocation;
473
474 fn local_label(&mut self, name: &'static str) {
475 if name.len() != 1 {
476 panic!("local label must be a single character");
477 }
478 let c = name.as_bytes()[0].wrapping_sub(b'A');
479 if c >= 26 {
480 panic!("Invalid label {name}, must be A-Z");
481 }
482 if self.local_labels[c as usize].is_some() {
483 panic!("duplicate local label {name}");
484 }
485
486 self.local_labels[c as usize] = Some(self.offset());
487 }
488 fn global_label(&mut self, name: &'static str) {
489 if name.len() != 1 {
490 panic!("local label must be a single character");
491 }
492 let c = name.as_bytes()[0].wrapping_sub(b'A');
493 if c >= 26 {
494 panic!("Invalid label {name}, must be A-Z");
495 }
496 if self.global_labels[c as usize].is_some() {
497 panic!("duplicate global label {name}");
498 }
499
500 self.global_labels[c as usize] = Some(self.offset());
501 }
502 fn dynamic_label(&mut self, _id: DynamicLabel) {
503 panic!("dynamic labels are not supported");
504 }
505 fn global_relocation(
506 &mut self,
507 name: &'static str,
508 target_offset: isize,
509 field_offset: u8,
510 ref_offset: u8,
511 kind: Relocation,
512 ) {
513 let location = self.offset();
514 if name.len() != 1 {
515 panic!("local label must be a single character");
516 }
517 let c = name.as_bytes()[0].wrapping_sub(b'A');
518 if c >= 26 {
519 panic!("Invalid label {name}, must be A-Z");
520 }
521 self.global_relocs.push((
522 PatchLoc::new(
523 location,
524 target_offset,
525 field_offset,
526 ref_offset,
527 kind,
528 ),
529 c,
530 ));
531 }
532 fn dynamic_relocation(
533 &mut self,
534 _id: DynamicLabel,
535 _target_offset: isize,
536 _field_offset: u8,
537 _ref_offset: u8,
538 _kind: Relocation,
539 ) {
540 panic!("dynamic relocations are not supported");
541 }
542 fn forward_relocation(
543 &mut self,
544 name: &'static str,
545 target_offset: isize,
546 field_offset: u8,
547 ref_offset: u8,
548 kind: Relocation,
549 ) {
550 if name.len() != 1 {
551 panic!("local label must be a single character");
552 }
553 let c = name.as_bytes()[0].wrapping_sub(b'A');
554 if c >= 26 {
555 panic!("Invalid label {name}, must be A-Z");
556 }
557 if self.local_labels[c as usize].is_some() {
558 panic!("invalid forward relocation: {name} already exists!");
559 }
560 let location = self.offset();
561 self.local_relocs.push((
562 PatchLoc::new(
563 location,
564 target_offset,
565 field_offset,
566 ref_offset,
567 kind,
568 ),
569 c,
570 ));
571 }
572 fn backward_relocation(
573 &mut self,
574 name: &'static str,
575 target_offset: isize,
576 field_offset: u8,
577 ref_offset: u8,
578 kind: Relocation,
579 ) {
580 if name.len() != 1 {
581 panic!("local label must be a single character");
582 }
583 let c = name.as_bytes()[0].wrapping_sub(b'A');
584 if c >= 26 {
585 panic!("Invalid label {name}, must be A-Z");
586 }
587 if self.local_labels[c as usize].is_none() {
588 panic!("invalid backward relocation: {name} does not exist");
589 }
590 let location = self.offset();
591 self.local_relocs.push((
592 PatchLoc::new(
593 location,
594 target_offset,
595 field_offset,
596 ref_offset,
597 kind,
598 ),
599 c,
600 ));
601 }
602 fn value_relocation(
603 &mut self,
604 _target: usize,
605 _field_offset: u8,
606 _ref_offset: u8,
607 _kind: Relocation,
608 ) {
609 panic!("bare relocations not implemented");
610 }
611}
612
613impl MmapAssembler {
614 fn commit_local(&mut self) -> Result<(), DynasmError> {
618 let baseaddr = self.mmap.as_ptr() as usize;
619
620 for (loc, label) in self.local_relocs.take() {
621 let target =
622 self.local_labels[label as usize].expect("invalid local label");
623 let buf = &mut self.mmap.as_mut_slice()[loc.range(0)];
624 if loc.patch(buf, baseaddr, target.0).is_err() {
625 return Err(DynasmError::ImpossibleRelocation(
626 TargetKind::Local("oh no"),
627 ));
628 }
629 }
630 self.local_labels = [None; 26];
631 Ok(())
632 }
633
634 fn finalize(mut self) -> Result<Mmap, DynasmError> {
635 self.commit_local()?;
636
637 let baseaddr = self.mmap.as_ptr() as usize;
638 for (loc, label) in self.global_relocs.take() {
639 let target =
640 self.global_labels.get(label as usize).unwrap().unwrap();
641 let buf = &mut self.mmap.as_mut_slice()[loc.range(0)];
642 if loc.patch(buf, baseaddr, target.0).is_err() {
643 return Err(DynasmError::ImpossibleRelocation(
644 TargetKind::Global("oh no"),
645 ));
646 }
647 }
648
649 Ok(self.mmap.finalize())
650 }
651}
652
653impl From<Mmap> for MmapAssembler {
654 fn from(mmap: Mmap) -> Self {
655 Self {
656 mmap: MmapWriter::from(mmap),
657 global_labels: [None; 26],
658 local_labels: [None; 26],
659 global_relocs: Default::default(),
660 local_relocs: Default::default(),
661 }
662 }
663}
664
665fn build_asm_fn_with_storage<A: Assembler>(
668 t: &VmData<REGISTER_LIMIT>,
669 mut s: Mmap,
670) -> Mmap {
671 let size_estimate = t.len() * A::bytes_per_clause();
672 if size_estimate > 2 * s.capacity() {
673 s = Mmap::new(size_estimate).expect("failed to build mmap")
674 }
675
676 let mut asm = A::init(s, t.slot_count());
677
678 for op in t.iter_asm() {
679 match op {
680 RegOp::Load(reg, mem) => {
681 asm.build_load(reg, mem);
682 }
683 RegOp::Store(reg, mem) => {
684 asm.build_store(mem, reg);
685 }
686 RegOp::Input(out, i) => {
687 asm.build_input(out, i);
688 }
689 RegOp::Output(arg, i) => {
690 asm.build_output(arg, i);
691 }
692 RegOp::NegReg(out, arg) => {
693 asm.build_neg(out, arg);
694 }
695 RegOp::AbsReg(out, arg) => {
696 asm.build_abs(out, arg);
697 }
698 RegOp::RecipReg(out, arg) => {
699 asm.build_recip(out, arg);
700 }
701 RegOp::SqrtReg(out, arg) => {
702 asm.build_sqrt(out, arg);
703 }
704 RegOp::SinReg(out, arg) => {
705 asm.build_sin(out, arg);
706 }
707 RegOp::CosReg(out, arg) => {
708 asm.build_cos(out, arg);
709 }
710 RegOp::TanReg(out, arg) => {
711 asm.build_tan(out, arg);
712 }
713 RegOp::AsinReg(out, arg) => {
714 asm.build_asin(out, arg);
715 }
716 RegOp::AcosReg(out, arg) => {
717 asm.build_acos(out, arg);
718 }
719 RegOp::AtanReg(out, arg) => {
720 asm.build_atan(out, arg);
721 }
722 RegOp::ExpReg(out, arg) => {
723 asm.build_exp(out, arg);
724 }
725 RegOp::LnReg(out, arg) => {
726 asm.build_ln(out, arg);
727 }
728 RegOp::CopyReg(out, arg) => {
729 asm.build_copy(out, arg);
730 }
731 RegOp::SquareReg(out, arg) => {
732 asm.build_square(out, arg);
733 }
734 RegOp::FloorReg(out, arg) => {
735 asm.build_floor(out, arg);
736 }
737 RegOp::CeilReg(out, arg) => {
738 asm.build_ceil(out, arg);
739 }
740 RegOp::RoundReg(out, arg) => {
741 asm.build_round(out, arg);
742 }
743 RegOp::NotReg(out, arg) => {
744 asm.build_not(out, arg);
745 }
746 RegOp::AddRegReg(out, lhs, rhs) => {
747 asm.build_add(out, lhs, rhs);
748 }
749 RegOp::MulRegReg(out, lhs, rhs) => {
750 asm.build_mul(out, lhs, rhs);
751 }
752 RegOp::DivRegReg(out, lhs, rhs) => {
753 asm.build_div(out, lhs, rhs);
754 }
755 RegOp::AtanRegReg(out, lhs, rhs) => {
756 asm.build_atan2(out, lhs, rhs);
757 }
758 RegOp::SubRegReg(out, lhs, rhs) => {
759 asm.build_sub(out, lhs, rhs);
760 }
761 RegOp::MinRegReg(out, lhs, rhs) => {
762 asm.build_min(out, lhs, rhs);
763 }
764 RegOp::MaxRegReg(out, lhs, rhs) => {
765 asm.build_max(out, lhs, rhs);
766 }
767 RegOp::AddRegImm(out, arg, imm) => {
768 asm.build_add_imm(out, arg, imm);
769 }
770 RegOp::MulRegImm(out, arg, imm) => {
771 asm.build_mul_imm(out, arg, imm);
772 }
773 RegOp::DivRegImm(out, arg, imm) => {
774 let reg = asm.load_imm(imm);
775 asm.build_div(out, arg, reg);
776 }
777 RegOp::DivImmReg(out, arg, imm) => {
778 let reg = asm.load_imm(imm);
779 asm.build_div(out, reg, arg);
780 }
781 RegOp::AtanRegImm(out, arg, imm) => {
782 let reg = asm.load_imm(imm);
783 asm.build_atan2(out, arg, reg);
784 }
785 RegOp::AtanImmReg(out, arg, imm) => {
786 let reg = asm.load_imm(imm);
787 asm.build_atan2(out, reg, arg);
788 }
789 RegOp::SubImmReg(out, arg, imm) => {
790 asm.build_sub_imm_reg(out, arg, imm);
791 }
792 RegOp::SubRegImm(out, arg, imm) => {
793 asm.build_sub_reg_imm(out, arg, imm);
794 }
795 RegOp::MinRegImm(out, arg, imm) => {
796 let reg = asm.load_imm(imm);
797 asm.build_min(out, arg, reg);
798 }
799 RegOp::MaxRegImm(out, arg, imm) => {
800 let reg = asm.load_imm(imm);
801 asm.build_max(out, arg, reg);
802 }
803 RegOp::ModRegReg(out, lhs, rhs) => {
804 asm.build_mod(out, lhs, rhs);
805 }
806 RegOp::ModRegImm(out, arg, imm) => {
807 let reg = asm.load_imm(imm);
808 asm.build_mod(out, arg, reg);
809 }
810 RegOp::ModImmReg(out, arg, imm) => {
811 let reg = asm.load_imm(imm);
812 asm.build_mod(out, reg, arg);
813 }
814 RegOp::AndRegReg(out, lhs, rhs) => {
815 asm.build_and(out, lhs, rhs);
816 }
817 RegOp::AndRegImm(out, arg, imm) => {
818 let reg = asm.load_imm(imm);
819 asm.build_and(out, arg, reg);
820 }
821 RegOp::OrRegReg(out, lhs, rhs) => {
822 asm.build_or(out, lhs, rhs);
823 }
824 RegOp::OrRegImm(out, arg, imm) => {
825 let reg = asm.load_imm(imm);
826 asm.build_or(out, arg, reg);
827 }
828 RegOp::CopyImm(out, imm) => {
829 let reg = asm.load_imm(imm);
830 asm.build_copy(out, reg);
831 }
832 RegOp::CompareRegReg(out, lhs, rhs) => {
833 asm.build_compare(out, lhs, rhs);
834 }
835 RegOp::CompareRegImm(out, arg, imm) => {
836 let reg = asm.load_imm(imm);
837 asm.build_compare(out, arg, reg);
838 }
839 RegOp::CompareImmReg(out, arg, imm) => {
840 let reg = asm.load_imm(imm);
841 asm.build_compare(out, reg, arg);
842 }
843 }
844 }
845
846 asm.finalize().expect("failed to build JIT function")
847 }
849
850#[derive(Clone)]
852pub struct JitFunction(GenericVmFunction<REGISTER_LIMIT>);
853
854impl JitFunction {
855 fn tracing_tape<A: Assembler>(
856 &self,
857 storage: Mmap,
858 ) -> JitTracingFn<A::Data> {
859 let f = build_asm_fn_with_storage::<A>(self.0.data(), storage);
860 let ptr = f.as_ptr();
861 JitTracingFn {
862 mmap: f.into(),
863 vars: self.0.data().vars.clone(),
864 choice_count: self.0.choice_count(),
865 output_count: self.0.output_count(),
866 fn_trace: unsafe {
867 std::mem::transmute::<
868 *const std::ffi::c_void,
869 JitTracingFnPointer<A::Data>,
870 >(ptr)
871 },
872 }
873 }
874 fn bulk_tape<A: Assembler>(&self, storage: Mmap) -> JitBulkFn<A::Data> {
875 let f = build_asm_fn_with_storage::<A>(self.0.data(), storage);
876 let ptr = f.as_ptr();
877 JitBulkFn {
878 mmap: f.into(),
879 output_count: self.0.output_count(),
880 vars: self.0.data().vars.clone(),
881 fn_bulk: unsafe {
882 std::mem::transmute::<
883 *const std::ffi::c_void,
884 JitBulkFnPointer<A::Data>,
885 >(ptr)
886 },
887 }
888 }
889}
890
891impl Function for JitFunction {
892 type Trace = VmTrace;
893 type Storage = VmData<REGISTER_LIMIT>;
894 type Workspace = VmWorkspace<REGISTER_LIMIT>;
895
896 type TapeStorage = Mmap;
897
898 type IntervalEval = JitIntervalEval;
899 type PointEval = JitPointEval;
900 type FloatSliceEval = JitFloatSliceEval;
901 type GradSliceEval = JitGradSliceEval;
902
903 #[inline]
904 fn point_tape(&self, storage: Mmap) -> JitTracingFn<f32> {
905 self.tracing_tape::<point::PointAssembler>(storage)
906 }
907
908 #[inline]
909 fn interval_tape(&self, storage: Mmap) -> JitTracingFn<Interval> {
910 self.tracing_tape::<interval::IntervalAssembler>(storage)
911 }
912
913 #[inline]
914 fn float_slice_tape(&self, storage: Mmap) -> JitBulkFn<f32> {
915 self.bulk_tape::<float_slice::FloatSliceAssembler>(storage)
916 }
917
918 #[inline]
919 fn grad_slice_tape(&self, storage: Mmap) -> JitBulkFn<Grad> {
920 self.bulk_tape::<grad_slice::GradSliceAssembler>(storage)
921 }
922
923 #[inline]
924 fn simplify(
925 &self,
926 trace: &Self::Trace,
927 storage: Self::Storage,
928 workspace: &mut Self::Workspace,
929 ) -> Result<Self, BadTrace> {
930 self.0.simplify(trace, storage, workspace).map(JitFunction)
931 }
932
933 #[inline]
934 fn recycle(self) -> Option<Self::Storage> {
935 self.0.recycle()
936 }
937
938 #[inline]
939 fn size(&self) -> usize {
940 self.0.size()
941 }
942
943 #[inline]
944 fn vars(&self) -> &VarMap {
945 self.0.vars()
946 }
947
948 #[inline]
949 fn can_simplify(&self) -> bool {
950 self.0.choice_count() > 0
951 }
952
953 #[inline]
954 fn output_count(&self) -> usize {
955 self.0.output_count()
956 }
957}
958
959impl RenderHints for JitFunction {
960 fn tile_sizes_3d() -> TileSizes {
961 TileSizes::new(&[64, 16, 8]).unwrap()
962 }
963
964 fn tile_sizes_2d() -> TileSizes {
965 TileSizes::new(&[128, 16]).unwrap()
966 }
967
968 fn simplify_tree_during_meshing(d: usize) -> bool {
969 d % 8 == 4
971 }
972}
973
974impl MathFunction for JitFunction {
975 fn new(ctx: &Context, nodes: &[Node]) -> Result<Self, BadNode> {
976 GenericVmFunction::new(ctx, nodes).map(JitFunction)
977 }
978}
979
980impl From<GenericVmFunction<REGISTER_LIMIT>> for JitFunction {
981 fn from(v: GenericVmFunction<REGISTER_LIMIT>) -> Self {
982 Self(v)
983 }
984}
985
986impl<'a> From<&'a JitFunction> for &'a GenericVmFunction<REGISTER_LIMIT> {
987 fn from(v: &'a JitFunction) -> Self {
988 &v.0
989 }
990}
991
992#[cfg(target_arch = "x86_64")]
1001macro_rules! jit_fn {
1002 (unsafe fn($($args:tt)*)) => {
1003 unsafe extern "sysv64" fn($($args)*)
1004 };
1005}
1006
1007#[cfg(target_arch = "aarch64")]
1011macro_rules! jit_fn {
1012 (unsafe fn($($args:tt)*)) => {
1013 unsafe extern "C" fn($($args)*)
1014 };
1015}
1016
1017struct JitTracingEval<T> {
1024 choices: VmTrace,
1025 out: Vec<T>,
1026}
1027
1028impl<T> Default for JitTracingEval<T> {
1029 fn default() -> Self {
1030 Self {
1031 choices: VmTrace::default(),
1032 out: Vec::default(),
1033 }
1034 }
1035}
1036
1037pub type JitTracingFnPointer<T> = jit_fn!(
1039 unsafe fn(
1040 *const T, *mut u8, *mut u8, *mut T, )
1045);
1046
1047#[derive(Clone)]
1049pub struct JitTracingFn<T> {
1050 mmap: Arc<Mmap>,
1051 choice_count: usize,
1052 output_count: usize,
1053 vars: Arc<VarMap>,
1054 fn_trace: JitTracingFnPointer<T>,
1055}
1056
1057impl<T: Clone> Tape for JitTracingFn<T> {
1058 type Storage = Mmap;
1059 fn recycle(self) -> Option<Self::Storage> {
1060 Arc::into_inner(self.mmap)
1061 }
1062
1063 fn vars(&self) -> &VarMap {
1064 &self.vars
1065 }
1066
1067 fn output_count(&self) -> usize {
1068 self.output_count
1069 }
1070}
1071
1072unsafe impl<T> Send for JitTracingFn<T> {}
1075unsafe impl<T> Sync for JitTracingFn<T> {}
1076
1077impl<T: From<f32> + Clone> JitTracingEval<T> {
1078 fn eval(
1080 &mut self,
1081 tape: &JitTracingFn<T>,
1082 vars: &[T],
1083 ) -> (&[T], Option<&VmTrace>) {
1084 let mut simplify = 0;
1085 self.choices.resize(tape.choice_count, Choice::Unknown);
1086 self.choices.fill(Choice::Unknown);
1087 self.out.resize(tape.output_count, f32::NAN.into());
1088 self.out.fill(f32::NAN.into());
1089 unsafe {
1090 (tape.fn_trace)(
1091 vars.as_ptr(),
1092 self.choices.as_mut_ptr() as *mut u8,
1093 &mut simplify,
1094 self.out.as_mut_ptr(),
1095 )
1096 };
1097
1098 (
1099 &self.out,
1100 if simplify != 0 {
1101 Some(&self.choices)
1102 } else {
1103 None
1104 },
1105 )
1106 }
1107}
1108
1109#[derive(Default)]
1111pub struct JitIntervalEval(JitTracingEval<Interval>);
1112impl TracingEvaluator for JitIntervalEval {
1113 type Data = Interval;
1114 type Tape = JitTracingFn<Interval>;
1115 type Trace = VmTrace;
1116 type TapeStorage = Mmap;
1117
1118 #[inline]
1119 fn eval(
1120 &mut self,
1121 tape: &Self::Tape,
1122 vars: &[Self::Data],
1123 ) -> Result<(&[Self::Data], Option<&Self::Trace>), TracingEvalError> {
1124 tape.vars().check_tracing_arguments(vars)?;
1125 Ok(self.0.eval(tape, vars))
1126 }
1127}
1128
1129#[derive(Default)]
1131pub struct JitPointEval(JitTracingEval<f32>);
1132impl TracingEvaluator for JitPointEval {
1133 type Data = f32;
1134 type Tape = JitTracingFn<f32>;
1135 type Trace = VmTrace;
1136 type TapeStorage = Mmap;
1137
1138 #[inline]
1139 fn eval(
1140 &mut self,
1141 tape: &Self::Tape,
1142 vars: &[Self::Data],
1143 ) -> Result<(&[Self::Data], Option<&Self::Trace>), TracingEvalError> {
1144 tape.vars().check_tracing_arguments(vars)?;
1145 Ok(self.0.eval(tape, vars))
1146 }
1147}
1148
1149pub type JitBulkFnPointer<T> = jit_fn!(
1153 unsafe fn(
1154 *const *const T, *const *mut T, u64, )
1158);
1159
1160#[derive(Clone)]
1162pub struct JitBulkFn<T> {
1163 mmap: Arc<Mmap>,
1164 vars: Arc<VarMap>,
1165 output_count: usize,
1166 fn_bulk: JitBulkFnPointer<T>,
1167}
1168
1169impl<T: Clone> Tape for JitBulkFn<T> {
1170 type Storage = Mmap;
1171 fn recycle(self) -> Option<Self::Storage> {
1172 Arc::into_inner(self.mmap)
1173 }
1174
1175 fn vars(&self) -> &VarMap {
1176 &self.vars
1177 }
1178
1179 fn output_count(&self) -> usize {
1180 self.output_count
1181 }
1182}
1183
1184const MAX_SIMD_WIDTH: usize = 8;
1191
1192struct JitBulkEval<T> {
1194 input_ptrs: Vec<*const T>,
1196
1197 output_ptrs: Vec<*mut T>,
1199
1200 scratch: Vec<[T; MAX_SIMD_WIDTH]>,
1202
1203 out: Vec<Vec<T>>,
1205}
1206
1207unsafe impl<T> Sync for JitBulkEval<T> {}
1210unsafe impl<T> Send for JitBulkEval<T> {}
1211
1212impl<T> Default for JitBulkEval<T> {
1213 fn default() -> Self {
1214 Self {
1215 out: vec![],
1216 scratch: vec![],
1217 input_ptrs: vec![],
1218 output_ptrs: vec![],
1219 }
1220 }
1221}
1222
1223unsafe impl<T> Send for JitBulkFn<T> {}
1226unsafe impl<T> Sync for JitBulkFn<T> {}
1227
1228impl<T: From<f32> + Copy + SimdSize> JitBulkEval<T> {
1229 fn eval<V: std::ops::Deref<Target = [T]>>(
1231 &mut self,
1232 tape: &JitBulkFn<T>,
1233 vars: &[V],
1234 ) -> BulkOutput<'_, T> {
1235 let n = vars.first().map(|v| v.deref().len()).unwrap_or(0);
1236
1237 self.out.resize_with(tape.output_count(), Vec::new);
1238 for o in &mut self.out {
1239 o.resize(n.max(T::SIMD_SIZE), f32::NAN.into());
1240 o.fill(f32::NAN.into());
1241 }
1242
1243 if n < T::SIMD_SIZE {
1247 assert!(T::SIMD_SIZE <= MAX_SIMD_WIDTH);
1248
1249 self.scratch
1250 .resize(vars.len(), [f32::NAN.into(); MAX_SIMD_WIDTH]);
1251 for (v, t) in vars.iter().zip(self.scratch.iter_mut()) {
1252 t[0..n].copy_from_slice(v);
1253 }
1254
1255 self.input_ptrs.clear();
1256 self.input_ptrs
1257 .extend(self.scratch[..vars.len()].iter().map(|t| t.as_ptr()));
1258
1259 self.output_ptrs.clear();
1260 self.output_ptrs
1261 .extend(self.out.iter_mut().map(|t| t.as_mut_ptr()));
1262
1263 unsafe {
1264 (tape.fn_bulk)(
1265 self.input_ptrs.as_ptr(),
1266 self.output_ptrs.as_ptr(),
1267 T::SIMD_SIZE as u64,
1268 );
1269 }
1270 } else {
1271 let m = (n / T::SIMD_SIZE) * T::SIMD_SIZE; self.input_ptrs.clear();
1276 self.input_ptrs.extend(vars.iter().map(|v| v.as_ptr()));
1277
1278 self.output_ptrs.clear();
1279 self.output_ptrs
1280 .extend(self.out.iter_mut().map(|v| v.as_mut_ptr()));
1281 unsafe {
1282 (tape.fn_bulk)(
1283 self.input_ptrs.as_ptr(),
1284 self.output_ptrs.as_ptr(),
1285 m as u64,
1286 );
1287 }
1288 if n != m {
1292 self.input_ptrs.clear();
1293 self.output_ptrs.clear();
1294 unsafe {
1295 self.input_ptrs.extend(
1296 vars.iter().map(|v| v.as_ptr().add(n - T::SIMD_SIZE)),
1297 );
1298 self.output_ptrs.extend(
1299 self.out
1300 .iter_mut()
1301 .map(|v| v.as_mut_ptr().add(n - T::SIMD_SIZE)),
1302 );
1303 (tape.fn_bulk)(
1304 self.input_ptrs.as_ptr(),
1305 self.output_ptrs.as_ptr(),
1306 T::SIMD_SIZE as u64,
1307 );
1308 }
1309 }
1310 }
1311 BulkOutput::new(&self.out, n)
1312 }
1313}
1314
1315#[derive(Default)]
1317pub struct JitFloatSliceEval(JitBulkEval<f32>);
1318impl BulkEvaluator for JitFloatSliceEval {
1319 type Data = f32;
1320 type Tape = JitBulkFn<Self::Data>;
1321 type TapeStorage = Mmap;
1322
1323 #[inline]
1324 fn eval<V: std::ops::Deref<Target = [Self::Data]>>(
1325 &mut self,
1326 tape: &Self::Tape,
1327 vars: &[V],
1328 ) -> Result<BulkOutput<'_, f32>, BulkEvalError> {
1329 tape.vars().check_bulk_arguments(vars)?;
1330 Ok(self.0.eval(tape, vars))
1331 }
1332}
1333
1334#[derive(Default)]
1336pub struct JitGradSliceEval(JitBulkEval<Grad>);
1337impl BulkEvaluator for JitGradSliceEval {
1338 type Data = Grad;
1339 type Tape = JitBulkFn<Self::Data>;
1340 type TapeStorage = Mmap;
1341
1342 #[inline]
1343 fn eval<V: std::ops::Deref<Target = [Self::Data]>>(
1344 &mut self,
1345 tape: &Self::Tape,
1346 vars: &[V],
1347 ) -> Result<BulkOutput<'_, Grad>, BulkEvalError> {
1348 tape.vars().check_bulk_arguments(vars)?;
1349 Ok(self.0.eval(tape, vars))
1350 }
1351}
1352
1353pub type JitShape = fidget_core::shape::Shape<JitFunction>;
1355
1356#[cfg(test)]
1359mod test {
1360 use super::*;
1361 fidget_core::grad_slice_tests!(JitFunction);
1362 fidget_core::interval_tests!(JitFunction);
1363 fidget_core::float_slice_tests!(JitFunction);
1364 fidget_core::point_tests!(JitFunction);
1365
1366 #[test]
1367 fn test_mmap_expansion() {
1368 let mmap = Mmap::new(0).unwrap();
1369
1370 let mut asm = MmapAssembler::from(mmap);
1371 const COUNT: u32 = 23456; for i in 0..COUNT {
1374 asm.push_u32(i);
1375 }
1376 let mmap = asm.finalize().unwrap();
1377 let ptr = mmap.as_ptr() as *const u32;
1378 for i in 0..COUNT {
1379 let v = unsafe { *ptr.add(i as usize) };
1380 assert_eq!(v, i);
1381 }
1382 }
1383}