1use crate::value::LocalValueId;
28
29use super::{
30 Callee,
31 mnemonic::{Args, MnemonicKind},
32};
33use smallvec::SmallVec;
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
39pub struct Scan {
40 pub body: Callee,
44 pub init: LocalValueId,
46 pub src: LocalValueId,
48 pub captures: Vec<LocalValueId>,
51}
52
53impl MnemonicKind for Scan {
54 fn opcode(&self) -> &'static str {
55 "scan"
56 }
57
58 fn args(&self) -> Args {
59 let mut args = SmallVec::with_capacity(2 + self.captures.len());
60 args.push(self.init);
61 args.push(self.src);
62 args.extend(self.captures.iter().copied());
63 args
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use crate::{
70 testing::TestContext,
71 value::{
72 FunctionBody, ValueId,
73 insn::{Callee, Mnemonic, mnemonic::MnemonicKind},
74 },
75 };
76
77 #[test]
80 fn scan_renders_as_scanl() {
81 let mut tc = TestContext::new();
82 let body = FunctionBody::make(&mut tc.ctx, "foo".into()).unwrap().id;
83 let host = FunctionBody::make(&mut tc.ctx, "host".into()).unwrap().id;
84 let entry = tc.ctx.get_or_make_block(0x2000, host);
85 {
86 let mut f = FunctionBody::from_id_mut(&mut tc.ctx, host);
87 f.set_root(entry).unwrap();
88 f.add_block(entry);
89 }
90 let i32_ty = tc.ctx.shared.types.get_or_make_int(4);
91 let array_ty = tc.ctx.shared.types.get_or_make_array(i32_ty, 8);
92 let (init, src, cap) = {
93 let mut b = tc.ctx.builder(entry);
94 (
95 b.push_param(4).id(),
96 b.push_param(32).id(),
97 b.push_param(4).id(),
98 )
99 };
100 if let ValueId::BlockParam(pid) = src {
101 tc.ctx.block_param_mut(pid).type_id = array_ty;
102 }
103
104 let plain = {
105 let mut b = tc.ctx.builder(entry);
106 b.push_scan(body, init, src, Vec::new()).id()
107 };
108 let ValueId::Instruction(plain_id) = plain else {
109 unreachable!()
110 };
111 let rendered = tc.ctx.get_insn(plain_id).as_statement().to_string();
112 assert!(
113 rendered.contains("scanl @foo"),
114 "scan renders as scanl, got: {rendered}"
115 );
116
117 let with_cap = {
118 let mut b = tc.ctx.builder(entry);
119 b.push_scan(body, init, src, vec![cap]).id()
120 };
121 let ValueId::Instruction(cap_id) = with_cap else {
122 unreachable!()
123 };
124 let rendered = tc.ctx.get_insn(cap_id).as_statement().to_string();
125 assert!(
126 rendered.contains("scanl (@foo "),
127 "a capturing scan renders as a partial application, got: {rendered}"
128 );
129 }
130
131 #[test]
135 fn scan_builds_with_array_result_and_symbol_body() {
136 let mut tc = TestContext::new();
137 let body = FunctionBody::make(&mut tc.ctx, "body".into()).unwrap().id;
138 let host = FunctionBody::make(&mut tc.ctx, "host".into()).unwrap().id;
139 let entry = tc.ctx.get_or_make_block(0x1000, host);
140 {
141 let mut f = FunctionBody::from_id_mut(&mut tc.ctx, host);
142 f.set_root(entry).unwrap();
143 f.add_block(entry);
144 }
145 let i32_ty = tc.ctx.shared.types.get_or_make_int(4);
146 let array_ty = tc.ctx.shared.types.get_or_make_array(i32_ty, 20);
147 let (init, src, cap) = {
148 let mut b = tc.ctx.builder(entry);
149 (
150 b.push_param(4).id(),
151 b.push_param(80).id(),
152 b.push_param(4).id(),
153 )
154 };
155 if let ValueId::BlockParam(pid) = src {
156 tc.ctx.block_param_mut(pid).type_id = array_ty;
157 }
158 let scan_val = {
159 let mut b = tc.ctx.builder(entry);
160 b.push_scan(body, init, src, vec![cap]).id()
161 };
162 let ValueId::Instruction(scan_id) = scan_val else {
163 panic!("push_scan should yield an instruction value");
164 };
165 let m = match tc.ctx.get_insn(scan_id).mnemonic().clone() {
166 Mnemonic::Scan(m) => m,
167 other => panic!("expected Scan, got {other:?}"),
168 };
169 assert_eq!(m.body, Callee::Real(body));
170 assert_eq!(
171 m.args().to_vec(),
172 vec![init.strip_func(), src.strip_func(), cap.strip_func()],
173 "init, src, then captures are the operands"
174 );
175 assert!(
176 !m.args().contains(&ValueId::Function(body).strip_func()),
177 "body is not an operand"
178 );
179 assert_eq!(tc.ctx.type_of(scan_val), array_ty);
181
182 let new_src = {
183 let mut b = tc.ctx.builder(entry);
184 b.push_param(80).id()
185 };
186 let mut rewritten = Mnemonic::Scan(m);
187 rewritten.replace_value(src.strip_func(), new_src.strip_func());
188 let Mnemonic::Scan(r) = rewritten else {
189 unreachable!()
190 };
191 assert_eq!(r.src, new_src.strip_func());
192 assert_eq!(
193 r.body,
194 Callee::Real(body),
195 "body symbol is untouched by replace_value"
196 );
197 assert_eq!(r.init, init.strip_func());
198 assert_eq!(r.captures, vec![cap.strip_func()]);
199 }
200}