1use crate::gas_cost::FastCost;
11
12pub struct GasSimulator {
14 reg_done: [u32; 13],
15 cycle: u32,
16 decode_used: u8,
17 max_done: u32,
18}
19
20impl Default for GasSimulator {
21 fn default() -> Self {
22 Self::new()
23 }
24}
25
26impl GasSimulator {
27 pub fn new() -> Self {
28 Self {
29 reg_done: [0; 13],
30 cycle: 0,
31 decode_used: 0,
32 max_done: 0,
33 }
34 }
35
36 #[inline(always)]
42 pub fn feed_direct(&mut self, cycles: u8, decode_slots: u8, src1: u8, src2: u8, dst: u8) {
43 if self.decode_used >= 4 {
47 self.cycle += 1;
48 self.decode_used = decode_slots;
49 } else {
50 self.decode_used += decode_slots;
51 }
52 let mut start = self.cycle;
53 if src1 < 13 {
54 start = start.max(self.reg_done[src1 as usize]);
55 }
56 if src2 < 13 {
57 start = start.max(self.reg_done[src2 as usize]);
58 }
59 let done = start + cycles as u32;
60 if dst < 13 {
61 self.reg_done[dst as usize] = done;
62 }
63 self.max_done = self.max_done.max(done);
64 }
65
66 #[inline]
68 pub fn feed(&mut self, cost: &FastCost) {
69 if self.decode_used >= 4 {
72 self.cycle += 1;
73 self.decode_used = cost.decode_slots;
74 } else {
75 self.decode_used += cost.decode_slots;
76 }
77
78 if cost.is_move_reg {
80 let src_reg = cost.src_mask.trailing_zeros() as usize;
81 let dst_reg = cost.dst_mask.trailing_zeros() as usize;
82 if src_reg < 13 && dst_reg < 13 {
83 self.reg_done[dst_reg] = self.reg_done[src_reg];
84 }
85 return;
86 }
87
88 let mut start = self.cycle;
90 let mut src = cost.src_mask;
91 while src != 0 {
92 let r = src.trailing_zeros() as usize;
93 src &= src - 1;
94 if r < 13 {
95 start = start.max(self.reg_done[r]);
96 }
97 }
98
99 let done = start + cost.cycles as u32;
101
102 let mut dst = cost.dst_mask;
104 while dst != 0 {
105 let r = dst.trailing_zeros() as usize;
106 dst &= dst - 1;
107 if r < 13 {
108 self.reg_done[r] = done;
109 }
110 }
111
112 self.max_done = self.max_done.max(done);
114 }
115
116 #[inline]
118 pub fn flush_and_get_cost(&self) -> u32 {
119 if self.max_done > 3 {
120 self.max_done - 3
121 } else {
122 1
123 }
124 }
125
126 #[inline]
128 pub fn reset(&mut self) {
129 self.reg_done = [0; 13];
130 self.cycle = 0;
131 self.decode_used = 0;
132 self.max_done = 0;
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
143 fn test_empty_block_cost_is_one() {
144 let sim = GasSimulator::new();
145 assert_eq!(sim.flush_and_get_cost(), 1, "empty block should cost 1");
146 }
147
148 #[test]
151 fn test_single_alu_instruction() {
152 let mut sim = GasSimulator::new();
154 sim.feed_direct(1, 1, 0, 0xFF, 2); assert_eq!(sim.flush_and_get_cost(), 1);
158 }
159
160 #[test]
161 fn test_data_dependency_chain() {
162 let mut sim = GasSimulator::new();
165 sim.feed_direct(1, 1, 0, 0xFF, 1); sim.feed_direct(1, 1, 1, 0xFF, 2); assert_eq!(sim.flush_and_get_cost(), 1);
169 }
170
171 #[test]
172 fn test_long_dependency_chain() {
173 let mut sim = GasSimulator::new();
175 for i in 0..5u8 {
176 sim.feed_direct(1, 1, i, 0xFF, i + 1);
177 }
178 assert_eq!(sim.flush_and_get_cost(), 2);
180 }
181
182 #[test]
183 fn test_independent_instructions_parallel() {
184 let mut sim = GasSimulator::new();
186 sim.feed_direct(1, 1, 0, 0xFF, 2);
187 sim.feed_direct(1, 1, 1, 0xFF, 3);
188 assert_eq!(sim.flush_and_get_cost(), 1);
191 }
192
193 #[test]
194 fn test_multi_cycle_instruction() {
195 let mut sim = GasSimulator::new();
197 sim.feed_direct(4, 1, 0, 1, 2); assert_eq!(sim.flush_and_get_cost(), 1);
200 }
201
202 #[test]
203 fn test_high_latency_chain() {
204 let mut sim = GasSimulator::new();
206 sim.feed_direct(4, 1, 0, 1, 2); sim.feed_direct(1, 1, 2, 0xFF, 3); assert_eq!(sim.flush_and_get_cost(), 2);
210 }
211
212 #[test]
213 fn test_decode_throughput_limit() {
214 let mut sim = GasSimulator::new();
216 for i in 0..5u8 {
217 sim.feed_direct(1, 1, 0xFF, 0xFF, i); }
219 assert_eq!(sim.flush_and_get_cost(), 1);
222 }
223
224 #[test]
225 fn test_no_src_no_dst() {
226 let mut sim = GasSimulator::new();
228 sim.feed_direct(1, 1, 0xFF, 0xFF, 0xFF);
229 assert_eq!(sim.flush_and_get_cost(), 1);
231 }
232
233 #[test]
234 fn test_two_sources() {
235 let mut sim = GasSimulator::new();
237 sim.feed_direct(3, 1, 0xFF, 0xFF, 1); sim.feed_direct(1, 1, 0, 1, 2); assert_eq!(sim.flush_and_get_cost(), 1);
242 }
243
244 #[test]
247 fn test_feed_move_reg_propagates_done() {
248 let mut sim = GasSimulator::new();
250 sim.feed_direct(3, 1, 0xFF, 0xFF, 0); sim.feed(&FastCost {
252 cycles: 0,
253 decode_slots: 1,
254 exec_unit: 0,
255 src_mask: 1 << 0, dst_mask: 1 << 1, is_terminator: false,
258 is_move_reg: true,
259 });
260 sim.feed_direct(1, 1, 1, 0xFF, 2); assert_eq!(sim.flush_and_get_cost(), 1);
265 }
266
267 #[test]
268 fn test_feed_bitmask_multiple_sources() {
269 let mut sim = GasSimulator::new();
270 sim.feed_direct(2, 1, 0xFF, 0xFF, 0); sim.feed_direct(3, 1, 0xFF, 0xFF, 1); sim.feed(&FastCost {
273 cycles: 1,
274 decode_slots: 1,
275 exec_unit: 1, src_mask: (1 << 0) | (1 << 1), dst_mask: 1 << 2, is_terminator: false,
279 is_move_reg: false,
280 });
281 assert_eq!(sim.flush_and_get_cost(), 1);
284 }
285
286 #[test]
289 fn test_reset_clears_state() {
290 let mut sim = GasSimulator::new();
291 sim.feed_direct(10, 1, 0xFF, 0xFF, 0); assert!(sim.flush_and_get_cost() > 1);
293 sim.reset();
294 assert_eq!(sim.flush_and_get_cost(), 1, "after reset, cost should be 1");
295 }
296}
297
298#[cfg(test)]
299mod proptests {
300 use super::*;
301 use proptest::prelude::*;
302
303 proptest! {
304 #[test]
306 fn cost_always_at_least_one(
307 instrs in proptest::collection::vec(
308 (1u8..20, 1u8..4, 0u8..13, 0u8..13),
309 0..10,
310 ),
311 ) {
312 let mut sim = GasSimulator::new();
313 for (cycles, slots, src, dst) in &instrs {
314 sim.feed_direct(*cycles, *slots, *src, 0xFF, *dst);
315 }
316 prop_assert!(sim.flush_and_get_cost() >= 1);
317 }
318
319 #[test]
321 fn reset_restores_empty_state(
322 instrs in proptest::collection::vec(
323 (1u8..20, 1u8..4, 0u8..13, 0u8..13),
324 1..10,
325 ),
326 ) {
327 let mut sim = GasSimulator::new();
328 for (cycles, slots, src, dst) in &instrs {
329 sim.feed_direct(*cycles, *slots, *src, 0xFF, *dst);
330 }
331 sim.reset();
332 prop_assert_eq!(sim.flush_and_get_cost(), 1);
333 }
334
335 #[test]
338 fn independent_no_more_than_chained(
339 count in 1usize..8,
340 cycles in 1u8..10,
341 ) {
342 let mut indep = GasSimulator::new();
344 for i in 0..count.min(13) {
345 indep.feed_direct(cycles, 1, 0xFF, 0xFF, i as u8);
346 }
347 let mut chain = GasSimulator::new();
349 for i in 0..count.min(12) {
350 chain.feed_direct(cycles, 1, i as u8, 0xFF, (i + 1) as u8);
351 }
352 prop_assert!(indep.flush_and_get_cost() <= chain.flush_and_get_cost());
353 }
354
355 #[test]
358 fn no_reg_deps_bounded_by_decode(
359 count in 1usize..20,
360 cycles in 1u8..5,
361 ) {
362 let mut sim = GasSimulator::new();
363 for _ in 0..count {
364 sim.feed_direct(cycles, 1, 0xFF, 0xFF, 0xFF);
365 }
366 let expected_max = ((count - 1) / 4) as u32 + cycles as u32;
370 let cost = sim.flush_and_get_cost();
371 let expected_cost = if expected_max > 3 { expected_max - 3 } else { 1 };
372 prop_assert_eq!(cost, expected_cost);
373 }
374
375 #[test]
378 fn feed_matches_feed_direct(
379 cycles in 1u8..20,
380 decode_slots in 1u8..4,
381 src in 0u8..13,
382 dst in 0u8..13,
383 ) {
384 let mut sim_direct = GasSimulator::new();
385 sim_direct.feed_direct(cycles, decode_slots, src, 0xFF, dst);
386
387 let mut sim_feed = GasSimulator::new();
388 sim_feed.feed(&FastCost {
389 cycles,
390 decode_slots,
391 exec_unit: 1,
392 src_mask: 1u16 << src,
393 dst_mask: 1u16 << dst,
394 is_terminator: false,
395 is_move_reg: false,
396 });
397
398 prop_assert_eq!(
399 sim_direct.flush_and_get_cost(),
400 sim_feed.flush_and_get_cost()
401 );
402 }
403
404 #[test]
406 fn cost_monotonic_with_instructions(
407 base_count in 1usize..6,
408 extra_count in 1usize..4,
409 cycles in 1u8..10,
410 ) {
411 let mut sim_base = GasSimulator::new();
412 let mut sim_more = GasSimulator::new();
413 for i in 0..base_count.min(12) {
414 sim_base.feed_direct(cycles, 1, i as u8, 0xFF, (i + 1) as u8);
415 sim_more.feed_direct(cycles, 1, i as u8, 0xFF, (i + 1) as u8);
416 }
417 let base_cost = sim_base.flush_and_get_cost();
418 let last = base_count.min(12);
419 for i in 0..extra_count.min(12 - last) {
420 sim_more.feed_direct(cycles, 1, (last + i) as u8, 0xFF, (last + i + 1).min(12) as u8);
421 }
422 prop_assert!(sim_more.flush_and_get_cost() >= base_cost);
423 }
424 }
425}