1use super::scan::{eval_single_op_f32, run_scan_packed_f32};
20use rlx_ir::{Graph, NodeId, Op, Shape};
21
22#[derive(Clone, Debug)]
25pub struct HostOpDesc {
26 pub op: Op,
27 pub out_byte_off: usize,
28 pub out_shape: Shape,
29 pub inputs: Vec<(usize, Shape)>,
31}
32
33pub fn host_op_desc_from_node(
36 graph: &Graph,
37 node: &rlx_ir::Node,
38 mut byte_off: impl FnMut(NodeId) -> usize,
39) -> HostOpDesc {
40 HostOpDesc {
41 op: node.op.clone(),
42 out_byte_off: byte_off(node.id),
43 out_shape: node.shape.clone(),
44 inputs: node
45 .inputs
46 .iter()
47 .map(|&id| (byte_off(id), graph.node(id).shape.clone()))
48 .collect(),
49 }
50}
51
52pub unsafe fn execute_host_op_on_bytes(base: *mut u8, desc: &HostOpDesc) {
57 let staged: Vec<(Shape, Vec<f32>)> = desc
58 .inputs
59 .iter()
60 .map(|(off, sh)| {
61 let n = sh.num_elements().unwrap_or(0);
62 let mut v = vec![0f32; n];
63 unsafe {
64 let src = base.add(*off) as *const f32;
65 std::ptr::copy_nonoverlapping(src, v.as_mut_ptr(), n);
66 }
67 (sh.clone(), v)
68 })
69 .collect();
70 let refs: Vec<(Shape, &[f32])> = staged
71 .iter()
72 .map(|(sh, v)| (sh.clone(), v.as_slice()))
73 .collect();
74 let y = eval_single_op_f32(&desc.op, &desc.out_shape, &refs);
75 unsafe {
76 let dst = base.add(desc.out_byte_off) as *mut f32;
77 std::ptr::copy_nonoverlapping(y.as_ptr(), dst, y.len());
78 }
79}
80
81pub fn eval_host_op_on_f32_arena(host: &mut [f32], desc: &HostOpDesc) {
84 debug_assert!(desc.out_byte_off.is_multiple_of(4));
85 let staged: Vec<(Shape, Vec<f32>)> = desc
86 .inputs
87 .iter()
88 .map(|(off, sh)| {
89 debug_assert!(off.is_multiple_of(4));
90 let off_f32 = *off / 4;
91 let n = sh.num_elements().unwrap_or(0);
92 let end = (off_f32 + n).min(host.len());
93 (sh.clone(), host[off_f32..end].to_vec())
94 })
95 .collect();
96 let refs: Vec<(Shape, &[f32])> = staged
97 .iter()
98 .map(|(sh, v)| (sh.clone(), v.as_slice()))
99 .collect();
100 let y = eval_single_op_f32(&desc.op, &desc.out_shape, &refs);
101 let out_f32 = desc.out_byte_off / 4;
102 let end = (out_f32 + y.len()).min(host.len());
103 host[out_f32..end].copy_from_slice(&y[..(end - out_f32)]);
104}
105
106pub fn run_scan_node_f32(node: &rlx_ir::Node, mut get: impl FnMut(NodeId) -> Vec<f32>) -> Vec<f32> {
108 let (body, length, save_trajectory, num_bcast, num_xs) = match &node.op {
109 Op::Scan {
110 body,
111 length,
112 save_trajectory,
113 num_bcast,
114 num_xs,
115 ..
116 } => (
117 body,
118 *length,
119 *save_trajectory,
120 *num_bcast as usize,
121 *num_xs as usize,
122 ),
123 _ => panic!("run_scan_node_f32: expected Op::Scan"),
124 };
125 let init = get(node.inputs[0]);
126 let mut bcasts = Vec::with_capacity(num_bcast);
127 for i in 0..num_bcast {
128 bcasts.push(get(node.inputs[1 + i]));
129 }
130 let mut xs = Vec::with_capacity(num_xs);
131 for i in 0..num_xs {
132 xs.push(get(node.inputs[1 + num_bcast + i]));
133 }
134 let out_len = node.shape.num_elements().unwrap_or(0);
135 run_scan_packed_f32(
136 body,
137 length,
138 save_trajectory,
139 num_bcast,
140 num_xs,
141 &init,
142 &bcasts,
143 &xs,
144 out_len,
145 )
146}
147
148pub fn run_host_op_node_f32(
151 graph: &Graph,
152 node: &rlx_ir::Node,
153 mut get: impl FnMut(NodeId) -> Vec<f32>,
154) -> Vec<f32> {
155 let staged: Vec<(Shape, Vec<f32>)> = node
156 .inputs
157 .iter()
158 .map(|&id| (graph.node(id).shape.clone(), get(id)))
159 .collect();
160 let refs: Vec<(Shape, &[f32])> = staged
161 .iter()
162 .map(|(sh, v)| (sh.clone(), v.as_slice()))
163 .collect();
164 eval_single_op_f32(&node.op, &node.shape, &refs)
165}
166
167#[macro_export]
169macro_rules! rlx_host_op_desc {
170 ($graph:expr, $node:expr, $byte_off:expr) => {
171 $crate::thunk::host_op_desc_from_node(&$graph, $node, $byte_off)
172 };
173}
174
175#[macro_export]
177macro_rules! rlx_execute_host_op_on_bytes {
178 ($base:expr, $desc:expr) => {
179 $crate::thunk::execute_host_op_on_bytes($base, $desc)
180 };
181}
182
183#[macro_export]
188macro_rules! rlx_host_op_stage_d2h {
189 (
190 arena_size_bytes = $nbytes:expr,
191 desc = $desc:expr,
192 sync = $sync:block,
193 dtoh = |$host:ident| $dtoh:block,
194 htod = |$host_back:ident| $htod:block $(,)?
195 ) => {
196 $crate::rlx_arena_stage_d2h! {
197 arena_size_bytes = $nbytes,
198 sync = $sync,
199 dtoh = |$host| $dtoh,
200 on_host = |host_mut| {
201 $crate::thunk::eval_host_op_on_f32_arena(host_mut, $desc);
202 },
203 htod = |$host_back| $htod,
204 }
205 };
206}
207
208#[derive(Clone)]
211pub struct HostOpSpan {
212 pub lo: usize,
213 pub hi: usize,
214 pub desc: HostOpDesc,
215}
216
217impl HostOpSpan {
218 pub fn from_desc(desc: HostOpDesc) -> Self {
219 let out_n = desc.out_shape.num_elements().unwrap_or(0) * 4;
220 let mut lo = desc.out_byte_off;
221 let mut hi = desc.out_byte_off + out_n;
222 for (off, sh) in &desc.inputs {
223 let n = sh.num_elements().unwrap_or(0) * 4;
224 lo = lo.min(*off);
225 hi = hi.max(*off + n);
226 }
227 let rebated = HostOpDesc {
228 op: desc.op,
229 out_byte_off: desc.out_byte_off - lo,
230 out_shape: desc.out_shape,
231 inputs: desc
232 .inputs
233 .into_iter()
234 .map(|(o, sh)| (o - lo, sh))
235 .collect(),
236 };
237 Self {
238 lo,
239 hi,
240 desc: rebated,
241 }
242 }
243
244 pub fn len(&self) -> usize {
245 self.hi - self.lo
246 }
247
248 pub fn is_empty(&self) -> bool {
249 self.len() == 0
250 }
251}