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_mix(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
181
182 fn build_square(&mut self, out_reg: u8, lhs_reg: u8) {
188 self.build_mul(out_reg, lhs_reg, lhs_reg)
189 }
190
191 fn build_floor(&mut self, out_reg: u8, lhs_reg: u8);
193
194 fn build_ceil(&mut self, out_reg: u8, lhs_reg: u8);
196
197 fn build_round(&mut self, out_reg: u8, lhs_reg: u8);
199
200 fn build_not(&mut self, out_reg: u8, lhs_reg: u8);
202
203 fn build_rand(&mut self, out_reg: u8, lhs_reg: u8);
205
206 fn build_and(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
208
209 fn build_or(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
211
212 fn build_add(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
214
215 fn build_sub(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
217
218 fn build_mul(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
220
221 fn build_div(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
223
224 fn build_atan2(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
226
227 fn build_max(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
232
233 fn build_min(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
238
239 fn build_mod(&mut self, out_reg: u8, lhs_reg: u8, rhs_reg: u8);
241
242 fn build_add_imm(&mut self, out_reg: u8, lhs_reg: u8, imm: f32) {
250 let imm = self.load_imm(imm);
251 self.build_add(out_reg, lhs_reg, imm);
252 }
253 fn build_sub_imm_reg(&mut self, out_reg: u8, arg: u8, imm: f32) {
257 let imm = self.load_imm(imm);
258 self.build_sub(out_reg, imm, arg);
259 }
260 fn build_sub_reg_imm(&mut self, out_reg: u8, arg: u8, imm: f32) {
264 let imm = self.load_imm(imm);
265 self.build_sub(out_reg, arg, imm);
266 }
267 fn build_mul_imm(&mut self, out_reg: u8, lhs_reg: u8, imm: f32) {
271 let imm = self.load_imm(imm);
272 self.build_mul(out_reg, lhs_reg, imm);
273 }
274
275 fn load_imm(&mut self, imm: f32) -> u8;
277
278 fn finalize(self) -> Result<Mmap, DynasmError>;
280}
281
282pub trait SimdSize {
284 const SIMD_SIZE: usize;
289}
290
291pub(crate) struct AssemblerData<T> {
294 ops: MmapAssembler,
295
296 mem_offset: usize,
298
299 saved_callee_regs: bool,
304
305 _p: std::marker::PhantomData<*const T>,
306}
307
308impl<T> AssemblerData<T> {
309 fn new(mmap: Mmap) -> Self {
310 Self {
311 ops: MmapAssembler::from(mmap),
312 mem_offset: 0,
313 saved_callee_regs: false,
314 _p: std::marker::PhantomData,
315 }
316 }
317
318 fn prepare_stack(&mut self, slot_count: usize, stack_size: usize) {
319 let mem = slot_count.saturating_sub(REGISTER_LIMIT)
321 * std::mem::size_of::<T>()
322 + stack_size;
323
324 self.mem_offset = mem.next_multiple_of(16);
326 self.push_stack();
327 }
328
329 fn stack_pos(&self, slot: u32) -> u32 {
330 assert!(slot >= REGISTER_LIMIT as u32);
331 (slot - REGISTER_LIMIT as u32) * std::mem::size_of::<T>() as u32
332 }
333}
334
335#[cfg(target_arch = "x86_64")]
336impl<T> AssemblerData<T> {
337 fn push_stack(&mut self) {
338 dynasm!(self.ops
339 ; sub rsp, self.mem_offset as i32
340 );
341 }
342
343 fn finalize(mut self) -> Result<Mmap, DynasmError> {
344 dynasm!(self.ops
345 ; add rsp, self.mem_offset as i32
346 ; pop rbp
347 ; vzeroupper
348 ; ret
349 );
350 self.ops.finalize()
351 }
352}
353
354#[cfg(target_arch = "aarch64")]
355#[allow(clippy::unnecessary_cast)] impl<T> AssemblerData<T> {
357 fn push_stack(&mut self) {
358 if self.mem_offset < 4096 {
359 dynasm!(self.ops
360 ; sub sp, sp, self.mem_offset as u32
361 );
362 } else if self.mem_offset < 65536 {
363 dynasm!(self.ops
364 ; mov w28, self.mem_offset as u32
365 ; sub sp, sp, w28
366 );
367 } else {
368 panic!("invalid mem offset: {} is too large", self.mem_offset);
369 }
370 }
371
372 fn finalize(mut self) -> Result<Mmap, DynasmError> {
373 if self.mem_offset < 4096 {
375 dynasm!(self.ops
376 ; add sp, sp, self.mem_offset as u32
377 );
378 } else if self.mem_offset < 65536 {
379 dynasm!(self.ops
380 ; mov w9, self.mem_offset as u32
381 ; add sp, sp, w9
382 );
383 } else {
384 panic!("invalid mem offset: {}", self.mem_offset);
385 }
386
387 dynasm!(self.ops
388 ; ret
389 );
390 self.ops.finalize()
391 }
392}
393
394#[cfg(target_arch = "x86_64")]
397type Relocation = dynasmrt::x64::X64Relocation;
398
399#[cfg(target_arch = "aarch64")]
400type Relocation = dynasmrt::aarch64::Aarch64Relocation;
401
402struct MmapAssembler {
403 mmap: MmapWriter,
404
405 global_labels: [Option<AssemblyOffset>; 26],
406 local_labels: [Option<AssemblyOffset>; 26],
407
408 global_relocs: arrayvec::ArrayVec<(PatchLoc<Relocation>, u8), 2>,
409 local_relocs: arrayvec::ArrayVec<(PatchLoc<Relocation>, u8), 8>,
410}
411
412impl Extend<u8> for MmapAssembler {
413 fn extend<T>(&mut self, iter: T)
414 where
415 T: IntoIterator<Item = u8>,
416 {
417 for c in iter.into_iter() {
418 self.push(c);
419 }
420 }
421}
422
423impl<'a> Extend<&'a u8> for MmapAssembler {
424 fn extend<T>(&mut self, iter: T)
425 where
426 T: IntoIterator<Item = &'a u8>,
427 {
428 for c in iter.into_iter() {
429 self.push(*c);
430 }
431 }
432}
433
434impl DynasmApi for MmapAssembler {
435 #[inline(always)]
436 fn offset(&self) -> AssemblyOffset {
437 AssemblyOffset(self.mmap.len())
438 }
439
440 #[inline(always)]
441 fn push(&mut self, byte: u8) {
442 self.mmap.push(byte);
443 }
444
445 #[inline(always)]
446 fn align(&mut self, alignment: usize, with: u8) {
447 let offset = self.offset().0 % alignment;
448 if offset != 0 {
449 for _ in offset..alignment {
450 self.push(with);
451 }
452 }
453 }
454
455 #[inline(always)]
456 fn push_u32(&mut self, value: u32) {
457 for b in value.to_le_bytes() {
458 self.mmap.push(b);
459 }
460 }
461}
462
463impl DynasmLabelApi for MmapAssembler {
478 type Relocation = Relocation;
479
480 fn local_label(&mut self, name: &'static str) {
481 if name.len() != 1 {
482 panic!("local label must be a single character");
483 }
484 let c = name.as_bytes()[0].wrapping_sub(b'A');
485 if c >= 26 {
486 panic!("Invalid label {name}, must be A-Z");
487 }
488 if self.local_labels[c as usize].is_some() {
489 panic!("duplicate local label {name}");
490 }
491
492 self.local_labels[c as usize] = Some(self.offset());
493 }
494 fn global_label(&mut self, name: &'static str) {
495 if name.len() != 1 {
496 panic!("local label must be a single character");
497 }
498 let c = name.as_bytes()[0].wrapping_sub(b'A');
499 if c >= 26 {
500 panic!("Invalid label {name}, must be A-Z");
501 }
502 if self.global_labels[c as usize].is_some() {
503 panic!("duplicate global label {name}");
504 }
505
506 self.global_labels[c as usize] = Some(self.offset());
507 }
508 fn dynamic_label(&mut self, _id: DynamicLabel) {
509 panic!("dynamic labels are not supported");
510 }
511 fn global_relocation(
512 &mut self,
513 name: &'static str,
514 target_offset: isize,
515 field_offset: u8,
516 ref_offset: u8,
517 kind: Relocation,
518 ) {
519 let location = self.offset();
520 if name.len() != 1 {
521 panic!("local label must be a single character");
522 }
523 let c = name.as_bytes()[0].wrapping_sub(b'A');
524 if c >= 26 {
525 panic!("Invalid label {name}, must be A-Z");
526 }
527 self.global_relocs.push((
528 PatchLoc::new(
529 location,
530 target_offset,
531 field_offset,
532 ref_offset,
533 kind,
534 ),
535 c,
536 ));
537 }
538 fn dynamic_relocation(
539 &mut self,
540 _id: DynamicLabel,
541 _target_offset: isize,
542 _field_offset: u8,
543 _ref_offset: u8,
544 _kind: Relocation,
545 ) {
546 panic!("dynamic relocations are not supported");
547 }
548 fn forward_relocation(
549 &mut self,
550 name: &'static str,
551 target_offset: isize,
552 field_offset: u8,
553 ref_offset: u8,
554 kind: Relocation,
555 ) {
556 if name.len() != 1 {
557 panic!("local label must be a single character");
558 }
559 let c = name.as_bytes()[0].wrapping_sub(b'A');
560 if c >= 26 {
561 panic!("Invalid label {name}, must be A-Z");
562 }
563 if self.local_labels[c as usize].is_some() {
564 panic!("invalid forward relocation: {name} already exists!");
565 }
566 let location = self.offset();
567 self.local_relocs.push((
568 PatchLoc::new(
569 location,
570 target_offset,
571 field_offset,
572 ref_offset,
573 kind,
574 ),
575 c,
576 ));
577 }
578 fn backward_relocation(
579 &mut self,
580 name: &'static str,
581 target_offset: isize,
582 field_offset: u8,
583 ref_offset: u8,
584 kind: Relocation,
585 ) {
586 if name.len() != 1 {
587 panic!("local label must be a single character");
588 }
589 let c = name.as_bytes()[0].wrapping_sub(b'A');
590 if c >= 26 {
591 panic!("Invalid label {name}, must be A-Z");
592 }
593 if self.local_labels[c as usize].is_none() {
594 panic!("invalid backward relocation: {name} does not exist");
595 }
596 let location = self.offset();
597 self.local_relocs.push((
598 PatchLoc::new(
599 location,
600 target_offset,
601 field_offset,
602 ref_offset,
603 kind,
604 ),
605 c,
606 ));
607 }
608 fn value_relocation(
609 &mut self,
610 _target: usize,
611 _field_offset: u8,
612 _ref_offset: u8,
613 _kind: Relocation,
614 ) {
615 panic!("bare relocations not implemented");
616 }
617}
618
619impl MmapAssembler {
620 fn commit_local(&mut self) -> Result<(), DynasmError> {
624 let baseaddr = self.mmap.as_ptr() as usize;
625
626 for (loc, label) in self.local_relocs.take() {
627 let target =
628 self.local_labels[label as usize].expect("invalid local label");
629 let buf = &mut self.mmap.as_mut_slice()[loc.range(0)];
630 if loc.patch(buf, baseaddr, target.0).is_err() {
631 return Err(DynasmError::ImpossibleRelocation(
632 TargetKind::Local("oh no"),
633 ));
634 }
635 }
636 self.local_labels = [None; 26];
637 Ok(())
638 }
639
640 fn finalize(mut self) -> Result<Mmap, DynasmError> {
641 self.commit_local()?;
642
643 let baseaddr = self.mmap.as_ptr() as usize;
644 for (loc, label) in self.global_relocs.take() {
645 let target =
646 self.global_labels.get(label as usize).unwrap().unwrap();
647 let buf = &mut self.mmap.as_mut_slice()[loc.range(0)];
648 if loc.patch(buf, baseaddr, target.0).is_err() {
649 return Err(DynasmError::ImpossibleRelocation(
650 TargetKind::Global("oh no"),
651 ));
652 }
653 }
654
655 Ok(self.mmap.finalize())
656 }
657}
658
659impl From<Mmap> for MmapAssembler {
660 fn from(mmap: Mmap) -> Self {
661 Self {
662 mmap: MmapWriter::from(mmap),
663 global_labels: [None; 26],
664 local_labels: [None; 26],
665 global_relocs: Default::default(),
666 local_relocs: Default::default(),
667 }
668 }
669}
670
671fn build_asm_fn_with_storage<A: Assembler>(
674 t: &VmData<REGISTER_LIMIT>,
675 mut s: Mmap,
676) -> Mmap {
677 let size_estimate = t.len() * A::bytes_per_clause();
678 if size_estimate > 2 * s.capacity() {
679 s = Mmap::new(size_estimate).expect("failed to build mmap")
680 }
681
682 let mut asm = A::init(s, t.slot_count());
683
684 for op in t.iter_asm() {
685 match op {
686 RegOp::Load(reg, mem) => {
687 asm.build_load(reg, mem);
688 }
689 RegOp::Store(reg, mem) => {
690 asm.build_store(mem, reg);
691 }
692 RegOp::Input(out, i) => {
693 asm.build_input(out, i);
694 }
695 RegOp::Output(arg, i) => {
696 asm.build_output(arg, i);
697 }
698 RegOp::NegReg(out, arg) => {
699 asm.build_neg(out, arg);
700 }
701 RegOp::AbsReg(out, arg) => {
702 asm.build_abs(out, arg);
703 }
704 RegOp::RecipReg(out, arg) => {
705 asm.build_recip(out, arg);
706 }
707 RegOp::SqrtReg(out, arg) => {
708 asm.build_sqrt(out, arg);
709 }
710 RegOp::SinReg(out, arg) => {
711 asm.build_sin(out, arg);
712 }
713 RegOp::CosReg(out, arg) => {
714 asm.build_cos(out, arg);
715 }
716 RegOp::TanReg(out, arg) => {
717 asm.build_tan(out, arg);
718 }
719 RegOp::AsinReg(out, arg) => {
720 asm.build_asin(out, arg);
721 }
722 RegOp::AcosReg(out, arg) => {
723 asm.build_acos(out, arg);
724 }
725 RegOp::AtanReg(out, arg) => {
726 asm.build_atan(out, arg);
727 }
728 RegOp::ExpReg(out, arg) => {
729 asm.build_exp(out, arg);
730 }
731 RegOp::LnReg(out, arg) => {
732 asm.build_ln(out, arg);
733 }
734 RegOp::CopyReg(out, arg) => {
735 asm.build_copy(out, arg);
736 }
737 RegOp::SquareReg(out, arg) => {
738 asm.build_square(out, arg);
739 }
740 RegOp::FloorReg(out, arg) => {
741 asm.build_floor(out, arg);
742 }
743 RegOp::CeilReg(out, arg) => {
744 asm.build_ceil(out, arg);
745 }
746 RegOp::RoundReg(out, arg) => {
747 asm.build_round(out, arg);
748 }
749 RegOp::NotReg(out, arg) => {
750 asm.build_not(out, arg);
751 }
752 RegOp::RandReg(out, arg) => {
753 asm.build_rand(out, arg);
754 }
755 RegOp::AddRegReg(out, lhs, rhs) => {
756 asm.build_add(out, lhs, rhs);
757 }
758 RegOp::MulRegReg(out, lhs, rhs) => {
759 asm.build_mul(out, lhs, rhs);
760 }
761 RegOp::DivRegReg(out, lhs, rhs) => {
762 asm.build_div(out, lhs, rhs);
763 }
764 RegOp::AtanRegReg(out, lhs, rhs) => {
765 asm.build_atan2(out, lhs, rhs);
766 }
767 RegOp::SubRegReg(out, lhs, rhs) => {
768 asm.build_sub(out, lhs, rhs);
769 }
770 RegOp::MinRegReg(out, lhs, rhs) => {
771 asm.build_min(out, lhs, rhs);
772 }
773 RegOp::MaxRegReg(out, lhs, rhs) => {
774 asm.build_max(out, lhs, rhs);
775 }
776 RegOp::AddRegImm(out, arg, imm) => {
777 asm.build_add_imm(out, arg, imm);
778 }
779 RegOp::MulRegImm(out, arg, imm) => {
780 asm.build_mul_imm(out, arg, imm);
781 }
782 RegOp::DivRegImm(out, arg, imm) => {
783 let reg = asm.load_imm(imm);
784 asm.build_div(out, arg, reg);
785 }
786 RegOp::DivImmReg(out, arg, imm) => {
787 let reg = asm.load_imm(imm);
788 asm.build_div(out, reg, arg);
789 }
790 RegOp::AtanRegImm(out, arg, imm) => {
791 let reg = asm.load_imm(imm);
792 asm.build_atan2(out, arg, reg);
793 }
794 RegOp::AtanImmReg(out, arg, imm) => {
795 let reg = asm.load_imm(imm);
796 asm.build_atan2(out, reg, arg);
797 }
798 RegOp::SubImmReg(out, arg, imm) => {
799 asm.build_sub_imm_reg(out, arg, imm);
800 }
801 RegOp::SubRegImm(out, arg, imm) => {
802 asm.build_sub_reg_imm(out, arg, imm);
803 }
804 RegOp::MinRegImm(out, arg, imm) => {
805 let reg = asm.load_imm(imm);
806 asm.build_min(out, arg, reg);
807 }
808 RegOp::MaxRegImm(out, arg, imm) => {
809 let reg = asm.load_imm(imm);
810 asm.build_max(out, arg, reg);
811 }
812 RegOp::ModRegReg(out, lhs, rhs) => {
813 asm.build_mod(out, lhs, rhs);
814 }
815 RegOp::ModRegImm(out, arg, imm) => {
816 let reg = asm.load_imm(imm);
817 asm.build_mod(out, arg, reg);
818 }
819 RegOp::ModImmReg(out, arg, imm) => {
820 let reg = asm.load_imm(imm);
821 asm.build_mod(out, reg, arg);
822 }
823 RegOp::AndRegReg(out, lhs, rhs) => {
824 asm.build_and(out, lhs, rhs);
825 }
826 RegOp::AndRegImm(out, arg, imm) => {
827 let reg = asm.load_imm(imm);
828 asm.build_and(out, arg, reg);
829 }
830 RegOp::OrRegReg(out, lhs, rhs) => {
831 asm.build_or(out, lhs, rhs);
832 }
833 RegOp::OrRegImm(out, arg, imm) => {
834 let reg = asm.load_imm(imm);
835 asm.build_or(out, arg, reg);
836 }
837 RegOp::CopyImm(out, imm) => {
838 let reg = asm.load_imm(imm);
839 asm.build_copy(out, reg);
840 }
841 RegOp::CompareRegReg(out, lhs, rhs) => {
842 asm.build_compare(out, lhs, rhs);
843 }
844 RegOp::CompareRegImm(out, arg, imm) => {
845 let reg = asm.load_imm(imm);
846 asm.build_compare(out, arg, reg);
847 }
848 RegOp::CompareImmReg(out, arg, imm) => {
849 let reg = asm.load_imm(imm);
850 asm.build_compare(out, reg, arg);
851 }
852 RegOp::MixRegReg(out, lhs, rhs) => {
853 asm.build_mix(out, lhs, rhs);
854 }
855 RegOp::MixRegImm(out, arg, imm) => {
856 let reg = asm.load_imm(imm);
857 asm.build_mix(out, arg, reg);
858 }
859 RegOp::MixImmReg(out, arg, imm) => {
860 let reg = asm.load_imm(imm);
861 asm.build_mix(out, reg, arg);
862 }
863 }
864 }
865
866 asm.finalize().expect("failed to build JIT function")
867 }
869
870#[derive(Clone)]
872pub struct JitFunction(GenericVmFunction<REGISTER_LIMIT>);
873
874impl JitFunction {
875 fn tracing_tape<A: Assembler>(
876 &self,
877 storage: Mmap,
878 ) -> JitTracingFn<A::Data> {
879 let f = build_asm_fn_with_storage::<A>(self.0.data(), storage);
880 let ptr = f.as_ptr();
881 JitTracingFn {
882 mmap: f.into(),
883 vars: self.0.data().vars.clone(),
884 choice_count: self.0.choice_count(),
885 output_count: self.0.output_count(),
886 fn_trace: unsafe {
887 std::mem::transmute::<
888 *const std::ffi::c_void,
889 JitTracingFnPointer<A::Data>,
890 >(ptr)
891 },
892 }
893 }
894 fn bulk_tape<A: Assembler>(&self, storage: Mmap) -> JitBulkFn<A::Data> {
895 let f = build_asm_fn_with_storage::<A>(self.0.data(), storage);
896 let ptr = f.as_ptr();
897 JitBulkFn {
898 mmap: f.into(),
899 output_count: self.0.output_count(),
900 vars: self.0.data().vars.clone(),
901 fn_bulk: unsafe {
902 std::mem::transmute::<
903 *const std::ffi::c_void,
904 JitBulkFnPointer<A::Data>,
905 >(ptr)
906 },
907 }
908 }
909}
910
911impl Function for JitFunction {
944 type Trace = VmTrace;
945 type Storage = VmData<REGISTER_LIMIT>;
946 type Workspace = VmWorkspace<REGISTER_LIMIT>;
947
948 type TapeStorage = Mmap;
949
950 type IntervalEval = JitIntervalEval;
951 type PointEval = JitPointEval;
952 type FloatSliceEval = JitFloatSliceEval;
953 type GradSliceEval = JitGradSliceEval;
954
955 #[inline]
956 fn point_tape(&self, storage: Mmap) -> JitTracingFn<f32> {
957 self.tracing_tape::<point::PointAssembler>(storage)
958 }
959
960 #[inline]
961 fn interval_tape(&self, storage: Mmap) -> JitTracingFn<Interval> {
962 self.tracing_tape::<interval::IntervalAssembler>(storage)
963 }
964
965 #[inline]
966 fn float_slice_tape(&self, storage: Mmap) -> JitBulkFn<f32> {
967 self.bulk_tape::<float_slice::FloatSliceAssembler>(storage)
968 }
969
970 #[inline]
971 fn grad_slice_tape(&self, storage: Mmap) -> JitBulkFn<Grad> {
972 self.bulk_tape::<grad_slice::GradSliceAssembler>(storage)
973 }
974
975 #[inline]
976 fn simplify(
977 &self,
978 trace: &Self::Trace,
979 storage: Self::Storage,
980 workspace: &mut Self::Workspace,
981 ) -> Result<Self, BadTrace> {
982 self.0.simplify(trace, storage, workspace).map(JitFunction)
983 }
984
985 #[inline]
986 fn recycle(self) -> Option<Self::Storage> {
987 self.0.recycle()
988 }
989
990 #[inline]
991 fn size(&self) -> usize {
992 self.0.size()
993 }
994
995 #[inline]
996 fn vars(&self) -> &VarMap {
997 self.0.vars()
998 }
999
1000 #[inline]
1001 fn can_simplify(&self) -> bool {
1002 self.0.choice_count() > 0
1003 }
1004
1005 #[inline]
1006 fn output_count(&self) -> usize {
1007 self.0.output_count()
1008 }
1009}
1010
1011impl RenderHints for JitFunction {
1012 fn tile_sizes_3d() -> TileSizes {
1013 TileSizes::new(&[64, 16, 8]).unwrap()
1014 }
1015
1016 fn tile_sizes_2d() -> TileSizes {
1017 TileSizes::new(&[128, 16]).unwrap()
1018 }
1019
1020 fn simplify_tree_during_meshing(d: usize) -> bool {
1021 d % 8 == 4
1023 }
1024}
1025
1026impl MathFunction for JitFunction {
1027 fn new(ctx: &Context, nodes: &[Node]) -> Result<Self, BadNode> {
1028 GenericVmFunction::new(ctx, nodes).map(JitFunction)
1029 }
1030}
1031
1032impl From<GenericVmFunction<REGISTER_LIMIT>> for JitFunction {
1033 fn from(v: GenericVmFunction<REGISTER_LIMIT>) -> Self {
1034 Self(v)
1035 }
1036}
1037
1038impl<'a> From<&'a JitFunction> for &'a GenericVmFunction<REGISTER_LIMIT> {
1039 fn from(v: &'a JitFunction) -> Self {
1040 &v.0
1041 }
1042}
1043
1044#[cfg(target_arch = "x86_64")]
1053macro_rules! jit_fn {
1054 (unsafe fn($($args:tt)*)) => {
1055 unsafe extern "sysv64" fn($($args)*)
1056 };
1057}
1058
1059#[cfg(target_arch = "aarch64")]
1063macro_rules! jit_fn {
1064 (unsafe fn($($args:tt)*)) => {
1065 unsafe extern "C" fn($($args)*)
1066 };
1067}
1068
1069struct JitTracingEval<T> {
1076 choices: VmTrace,
1077 out: Vec<T>,
1078}
1079
1080impl<T> Default for JitTracingEval<T> {
1081 fn default() -> Self {
1082 Self {
1083 choices: VmTrace::default(),
1084 out: Vec::default(),
1085 }
1086 }
1087}
1088
1089pub type JitTracingFnPointer<T> = jit_fn!(
1091 unsafe fn(
1092 *const T, *mut u8, *mut u8, *mut T, )
1097);
1098
1099#[derive(Clone)]
1101pub struct JitTracingFn<T> {
1102 mmap: Arc<Mmap>,
1103 choice_count: usize,
1104 output_count: usize,
1105 vars: Arc<VarMap>,
1106 fn_trace: JitTracingFnPointer<T>,
1107}
1108
1109impl<T: Clone> Tape for JitTracingFn<T> {
1110 type Storage = Mmap;
1111 fn recycle(self) -> Option<Self::Storage> {
1112 Arc::into_inner(self.mmap)
1113 }
1114
1115 fn vars(&self) -> &VarMap {
1116 &self.vars
1117 }
1118
1119 fn output_count(&self) -> usize {
1120 self.output_count
1121 }
1122}
1123
1124unsafe impl<T> Send for JitTracingFn<T> {}
1127unsafe impl<T> Sync for JitTracingFn<T> {}
1128
1129impl<T: From<f32> + Clone> JitTracingEval<T> {
1130 fn eval(
1132 &mut self,
1133 tape: &JitTracingFn<T>,
1134 vars: &[T],
1135 ) -> (&[T], Option<&VmTrace>) {
1136 let mut simplify = 0;
1137 self.choices.resize(tape.choice_count, Choice::Unknown);
1138 self.choices.fill(Choice::Unknown);
1139 self.out.resize(tape.output_count, f32::NAN.into());
1140 self.out.fill(f32::NAN.into());
1141 unsafe {
1142 (tape.fn_trace)(
1143 vars.as_ptr(),
1144 self.choices.as_mut_ptr() as *mut u8,
1145 &mut simplify,
1146 self.out.as_mut_ptr(),
1147 )
1148 };
1149
1150 (
1151 &self.out,
1152 if simplify != 0 {
1153 Some(&self.choices)
1154 } else {
1155 None
1156 },
1157 )
1158 }
1159}
1160
1161#[derive(Default)]
1163pub struct JitIntervalEval(JitTracingEval<Interval>);
1164impl TracingEvaluator for JitIntervalEval {
1165 type Data = Interval;
1166 type Tape = JitTracingFn<Interval>;
1167 type Trace = VmTrace;
1168 type TapeStorage = Mmap;
1169
1170 #[inline]
1171 fn eval(
1172 &mut self,
1173 tape: &Self::Tape,
1174 vars: &[Self::Data],
1175 ) -> Result<(&[Self::Data], Option<&Self::Trace>), TracingEvalError> {
1176 tape.vars().check_tracing_arguments(vars)?;
1177 Ok(self.0.eval(tape, vars))
1178 }
1179}
1180
1181#[derive(Default)]
1183pub struct JitPointEval(JitTracingEval<f32>);
1184impl TracingEvaluator for JitPointEval {
1185 type Data = f32;
1186 type Tape = JitTracingFn<f32>;
1187 type Trace = VmTrace;
1188 type TapeStorage = Mmap;
1189
1190 #[inline]
1191 fn eval(
1192 &mut self,
1193 tape: &Self::Tape,
1194 vars: &[Self::Data],
1195 ) -> Result<(&[Self::Data], Option<&Self::Trace>), TracingEvalError> {
1196 tape.vars().check_tracing_arguments(vars)?;
1197 Ok(self.0.eval(tape, vars))
1198 }
1199}
1200
1201pub type JitBulkFnPointer<T> = jit_fn!(
1205 unsafe fn(
1206 *const *const T, *const *mut T, u64, )
1210);
1211
1212#[derive(Clone)]
1214pub struct JitBulkFn<T> {
1215 mmap: Arc<Mmap>,
1216 vars: Arc<VarMap>,
1217 output_count: usize,
1218 fn_bulk: JitBulkFnPointer<T>,
1219}
1220
1221impl<T: Clone> Tape for JitBulkFn<T> {
1222 type Storage = Mmap;
1223 fn recycle(self) -> Option<Self::Storage> {
1224 Arc::into_inner(self.mmap)
1225 }
1226
1227 fn vars(&self) -> &VarMap {
1228 &self.vars
1229 }
1230
1231 fn output_count(&self) -> usize {
1232 self.output_count
1233 }
1234}
1235
1236const MAX_SIMD_WIDTH: usize = 8;
1243
1244struct JitBulkEval<T> {
1246 input_ptrs: Vec<*const T>,
1248
1249 output_ptrs: Vec<*mut T>,
1251
1252 scratch: Vec<[T; MAX_SIMD_WIDTH]>,
1254
1255 out: Vec<Vec<T>>,
1257}
1258
1259unsafe impl<T> Sync for JitBulkEval<T> {}
1262unsafe impl<T> Send for JitBulkEval<T> {}
1263
1264impl<T> Default for JitBulkEval<T> {
1265 fn default() -> Self {
1266 Self {
1267 out: vec![],
1268 scratch: vec![],
1269 input_ptrs: vec![],
1270 output_ptrs: vec![],
1271 }
1272 }
1273}
1274
1275unsafe impl<T> Send for JitBulkFn<T> {}
1278unsafe impl<T> Sync for JitBulkFn<T> {}
1279
1280impl<T: From<f32> + Copy + SimdSize> JitBulkEval<T> {
1281 fn eval<V: std::ops::Deref<Target = [T]>>(
1283 &mut self,
1284 tape: &JitBulkFn<T>,
1285 vars: &[V],
1286 ) -> BulkOutput<'_, T> {
1287 let n = vars.first().map(|v| v.deref().len()).unwrap_or(0);
1288
1289 self.out.resize_with(tape.output_count(), Vec::new);
1290 for o in &mut self.out {
1291 o.resize(n.max(T::SIMD_SIZE), f32::NAN.into());
1292 o.fill(f32::NAN.into());
1293 }
1294
1295 if n < T::SIMD_SIZE {
1299 assert!(T::SIMD_SIZE <= MAX_SIMD_WIDTH);
1300
1301 self.scratch
1302 .resize(vars.len(), [f32::NAN.into(); MAX_SIMD_WIDTH]);
1303 for (v, t) in vars.iter().zip(self.scratch.iter_mut()) {
1304 t[0..n].copy_from_slice(v);
1305 }
1306
1307 self.input_ptrs.clear();
1308 self.input_ptrs
1309 .extend(self.scratch[..vars.len()].iter().map(|t| t.as_ptr()));
1310
1311 self.output_ptrs.clear();
1312 self.output_ptrs
1313 .extend(self.out.iter_mut().map(|t| t.as_mut_ptr()));
1314
1315 unsafe {
1316 (tape.fn_bulk)(
1317 self.input_ptrs.as_ptr(),
1318 self.output_ptrs.as_ptr(),
1319 T::SIMD_SIZE as u64,
1320 );
1321 }
1322 } else {
1323 let m = (n / T::SIMD_SIZE) * T::SIMD_SIZE; self.input_ptrs.clear();
1328 self.input_ptrs.extend(vars.iter().map(|v| v.as_ptr()));
1329
1330 self.output_ptrs.clear();
1331 self.output_ptrs
1332 .extend(self.out.iter_mut().map(|v| v.as_mut_ptr()));
1333 unsafe {
1334 (tape.fn_bulk)(
1335 self.input_ptrs.as_ptr(),
1336 self.output_ptrs.as_ptr(),
1337 m as u64,
1338 );
1339 }
1340 if n != m {
1344 self.input_ptrs.clear();
1345 self.output_ptrs.clear();
1346 unsafe {
1347 self.input_ptrs.extend(
1348 vars.iter().map(|v| v.as_ptr().add(n - T::SIMD_SIZE)),
1349 );
1350 self.output_ptrs.extend(
1351 self.out
1352 .iter_mut()
1353 .map(|v| v.as_mut_ptr().add(n - T::SIMD_SIZE)),
1354 );
1355 (tape.fn_bulk)(
1356 self.input_ptrs.as_ptr(),
1357 self.output_ptrs.as_ptr(),
1358 T::SIMD_SIZE as u64,
1359 );
1360 }
1361 }
1362 }
1363 BulkOutput::new(&self.out, n)
1364 }
1365}
1366
1367#[derive(Default)]
1369pub struct JitFloatSliceEval(JitBulkEval<f32>);
1370impl BulkEvaluator for JitFloatSliceEval {
1371 type Data = f32;
1372 type Tape = JitBulkFn<Self::Data>;
1373 type TapeStorage = Mmap;
1374
1375 #[inline]
1376 fn eval<V: std::ops::Deref<Target = [Self::Data]>>(
1377 &mut self,
1378 tape: &Self::Tape,
1379 vars: &[V],
1380 ) -> Result<BulkOutput<'_, f32>, BulkEvalError> {
1381 tape.vars().check_bulk_arguments(vars)?;
1382 Ok(self.0.eval(tape, vars))
1383 }
1384}
1385
1386#[derive(Default)]
1388pub struct JitGradSliceEval(JitBulkEval<Grad>);
1389impl BulkEvaluator for JitGradSliceEval {
1390 type Data = Grad;
1391 type Tape = JitBulkFn<Self::Data>;
1392 type TapeStorage = Mmap;
1393
1394 #[inline]
1395 fn eval<V: std::ops::Deref<Target = [Self::Data]>>(
1396 &mut self,
1397 tape: &Self::Tape,
1398 vars: &[V],
1399 ) -> Result<BulkOutput<'_, Grad>, BulkEvalError> {
1400 tape.vars().check_bulk_arguments(vars)?;
1401 Ok(self.0.eval(tape, vars))
1402 }
1403}
1404
1405pub type JitShape = fidget_core::shape::Shape<JitFunction>;
1407
1408#[cfg(test)]
1411mod test {
1412 use super::*;
1413 fidget_core::grad_slice_tests!(JitFunction);
1414 fidget_core::interval_tests!(JitFunction);
1415 fidget_core::float_slice_tests!(JitFunction);
1416 fidget_core::point_tests!(JitFunction);
1417
1418 #[test]
1419 fn test_mmap_expansion() {
1420 let mmap = Mmap::new(0).unwrap();
1421
1422 let mut asm = MmapAssembler::from(mmap);
1423 const COUNT: u32 = 23456; for i in 0..COUNT {
1426 asm.push_u32(i);
1427 }
1428 let mmap = asm.finalize().unwrap();
1429 let ptr = mmap.as_ptr() as *const u32;
1430 for i in 0..COUNT {
1431 let v = unsafe { *ptr.add(i as usize) };
1432 assert_eq!(v, i);
1433 }
1434 }
1435}