1use super::super::utils::*;
10use std::{
11 fs::{create_dir_all, File},
12 io::{BufWriter, Error, Write},
13 writeln as w,
14};
15
16const LOWER_LIMIT: usize = 16;
17
18#[rustfmt::skip] const MAX_RECURSION: usize = {
19 if cfg!(not(feature = "_unroll_128")) { 64
20 } else if cfg!(all(feature = "_unroll_128", not(feature = "_unroll_256"))) { 128
21 } else if cfg!(all(feature = "_unroll_256", not(feature = "_unroll_512"))) { 256
22 } else if cfg!(all(feature = "_unroll_512", not(feature = "_unroll_1024"))) { 512
23 } else if cfg!(all(feature = "_unroll_1024", not(feature = "_unroll_2048"))) { 1024
24 } else { 2048 }
25};
26
27#[rustfmt::skip]
28pub(crate) fn generate() -> Result<(), Error> {
29 let build_out_dir = out_dir().join("build/");
30 create_dir_all(&build_out_dir)?;
31 let path = build_out_dir.join("unroll.rs");
32
33 #[cfg(feature = "__dbg")]
35 println(&format!("generated: {}", path.display()));
36
37 let file = File::create(path)?;
38 let mut f = BufWriter::new(file);
39 let macro_code1 = r#"/// Unrolls the given for loop.
42///
43/// # Example
44/// ```ignore
45/// unroll! {
46/// for i in 0..5 {
47/// println!("Iteration {}", i);
48/// }
49/// }
50/// ```
51///
52/// will expand into:
53/// ```ignore
54/// { println!("Iteration {}", 0); }
55/// { println!("Iteration {}", 1); }
56/// { println!("Iteration {}", 2); }
57/// { println!("Iteration {}", 3); }
58/// { println!("Iteration {}", 4); }
59/// ```
60///
61/// # Features
62/// By default it's implemented for a maximum recusion of 64 iterations.
63/// It supports increased limits of 128, 256, 512, 1024 and 2048 by enabling the
64/// corresponding capability feature: `_unroll_[128|256|512|1024|2048]`.
65///
66/// # Vendored
67/// This is adapted work from [crunchy][crate::_info::vendored#crunchy]"#;
68w!(f, "{0}", macro_code1)?;
70 let macro_code2 = r#"#[doc(hidden)]
71#[macro_export]
72macro_rules! _unroll {
73 (
74 // Base case for ranges with no iterations.
75 for $v:ident in 0..0 $c:block) => {};
76 (
77 // Handles ranges with a step value.
78 for $v:ident < $max:tt in ($start:tt..$end:tt).step_by($val:expr) {$($c:tt)*}) => {
79 // Expands the loop by calculating the stepped range and recursively unrolling.
80 {
81 let step = $val;
82 let start = $start;
83 let end = start + ($end - start) / step;
84 $crate::unroll! {
85 for val < $max in start..end {
86 let $v: usize = ((val.wrapping_sub(start)) * step) + start;
87 $($c)*
88 }
89 }
90 }
91 };
92 (
93 // Redirects stepped ranges.
94 for $v:ident in ($start:tt..$end:tt).step_by($val:expr) {$($c:tt)*}) => {
95 $crate::unroll! {
96 for $v < $end in ($start..$end).step_by($val) {$($c)*}
97 }
98 };
99 (
100 // Simplifies parentheses in ranges.
101 for $v:ident in ($start:tt..$end:tt) {$($c:tt)*}) => {
102 $crate::unroll!{ for $v in $start..$end {$($c)*} }
103 };
104 (
105 // Main handler for unrolling a range.
106 for $v:ident in $start:tt..$end:tt {$($c:tt)*}) => {
107 // Calls an internal recursive macro to expand the loop.
108 #[allow(non_upper_case_globals)]
109 #[allow(unused_comparisons)]
110 { $crate::unroll![@$v, 0, $end, { if $v >= $start {$($c)*} }]; }
111 };
112 (
113 // Validates the range and redirects to internal recursive unrolling with bounds checking.
114 for $v:ident < $max:tt in $start:tt..$end:tt $c:block) => {
115 #[allow(non_upper_case_globals)]
116 {
117 let range = $start..$end;
118 assert!($max >= range.end, "`{0}` out of range `{1:?}`", stringify!($max), range,);
119 $crate::unroll![@$v, 0, $max, { if $v >= range.start && $v < range.end { $c } }];
120 }
121 };
122 (
123 // Special case for ranges starting at zero.
124 for $v:ident in 0..$end:tt {$($statement:tt)*}) => {
125 // Calls the internal recursive unrolling macro.
126 #[allow(non_upper_case_globals)]
127 { $crate::unroll![@$v, 0, $end, {$($statement)*}]; }
128 };
129 (
130 /* private, recursive unrolling cases */
131 @$v:ident, $a:expr, 0, $c:block) => {
132 { const $v: usize = $a; $c }
133 };
134"#;
135 w!(f, "{0}", macro_code2)?;
136
137 for i in 1..MAX_RECURSION + 1 {
138 w!(f, " (@$v:ident, $a:expr, {0}, $c:block) => {{", i)?;
139 if i <= LOWER_LIMIT {
140 w!(f, " {{ const $v: usize = $a; $c }}")?;
141 for a in 1..i {
142 w!(f, " {{ const $v: usize = $a + {0}; $c }}", a)?;
143 }
144 } else {
145 let half = i / 2;
146
147 if i % 2 == 0 {
148 w!(f, " $crate::unroll![@$v, $a, {0}, $c];", half)?;
149 w!(f, " $crate::unroll![@$v, $a + {0}, {0}, $c];", half)?;
150 } else {
151 if half > 1 {
152 w!(f, " $crate::unroll![@$v, $a, {0}, $c];", i - 1)?;
153 }
154 w!(f, " {{ const $v: usize = $a + {0}; $c }}", i - 1)?;
155 }
156 }
157 w!(f, " }};")?;
158 }
159 w!(f, "}}\n#[doc(inline)]\npub use _unroll as unroll;")?;
160
161 let tests_code1 = r#"
164#[cfg(all(test, feature = "alloc"))]
165mod tests {
166 use crate::{unroll, vec_ as vec, Vec};
167
168 #[test]
169 fn invalid_range() {
170 let mut a: Vec<usize> = vec![];
171 unroll! {
172 for i in (5..4) {
173 a.push(i);
174 }
175 }
176 assert!(a.is_empty());
177 }
178
179 #[test]
180 fn start_at_one_with_step() {
181 let mut a: Vec<usize> = vec![];
182 unroll! {
183 for i in (2..4).step_by(1) {
184 a.push(i);
185 }
186 }
187 assert_eq!(a, vec![2, 3]);
188 }
189
190 #[test]
191 fn start_at_one() {
192 let mut a: Vec<usize> = vec![];
193 unroll! {
194 for i in 1..4 {
195 a.push(i);
196 }
197 }
198 assert_eq!(a, vec![1, 2, 3]);
199 }
200
201 #[test]
202 fn test_all() {
203 {
204 let a: Vec<usize> = vec![];
205 unroll! {
206 for i in 0..0 {
207 a.push(i);
208 }
209 }
210 assert_eq!(a, (0..0).collect::<Vec<usize>>());
211 }
212 {
213 let mut a: Vec<usize> = vec![];
214 unroll! {
215 for i in 0..1 {
216 a.push(i);
217 }
218 }
219 assert_eq!(a, (0..1).collect::<Vec<usize>>());
220 }"#;
221 w!(f, "{0}", tests_code1)?;
222
223 w!(f, r#"
224 {{
225 let mut a: Vec<usize> = vec![];
226 unroll! {{
227 for i in 0..{0} {{
228 a.push(i);
229 }}
230 }}
231 assert_eq!(a, (0..{0}).collect::<Vec<usize>>());
232 }}
233 {{
234 let mut a: Vec<usize> = vec![];
235 let start = {0} / 4;
236 let end = start * 3;
237 unroll! {{
238 for i < {0} in start..end {{
239 a.push(i);
240 }}
241 }}
242 assert_eq!(a, (start..end).collect::<Vec<usize>>());
243 }}
244 {{
245 let mut a: Vec<usize> = vec![];
246 unroll! {{
247 for i in (0..{0}).step_by(2) {{
248 a.push(i);
249 }}
250 }}
251 assert_eq!(a, (0..{0} / 2).map(|x| x * 2).collect::<Vec<usize>>());
252 }}
253 {{
254 let mut a: Vec<usize> = vec![];
255 let start = {0} / 4;
256 let end = start * 3;
257 unroll! {{
258 for i < {0} in (start..end).step_by(2) {{
259 a.push(i);
260 }}
261 }}
262 assert_eq!(a, (start..end).filter(|x| x % 2 == 0).collect::<Vec<usize>>());
263 }}
264 }}
265}}"#, MAX_RECURSION)?;
266
267 if let Err(e) = f.flush() {
270 eprintln!("Failed to write to file: {0}", e);
271 std::process::exit(1);
272 }
273
274 Ok(())
277}