Some tweaks to Lua docs
[lsnes.git] / src / library / memorysearch.cpp
blob911e66237ee9c179bb1738597d5e9bc3c1dc60dd
1 #include "memorysearch.hpp"
2 #include "eatarg.hpp"
3 #include "minmax.hpp"
4 #include "serialization.hpp"
5 #include "int24.hpp"
6 #include <iostream>
8 memory_search::memory_search(memory_space& space) throw(std::bad_alloc)
9 : mspace(space)
11 candidates = 0;
15 struct search_update
17 typedef uint8_t value_type;
18 bool operator()(uint8_t oldv, uint8_t newv) const throw() { return true; }
21 template<typename T>
22 struct search_value
24 typedef T value_type;
25 search_value(T v) throw() { val = v; }
26 bool operator()(T oldv, T newv) const throw() { return (newv == val); }
27 T val;
30 template<typename T>
31 struct search_difference
33 typedef T value_type;
34 search_difference(T v) throw() { val = v; }
35 bool operator()(T oldv, T newv) const throw() { return ((newv - oldv) == val); }
36 T val;
39 template<typename T>
40 struct search_lt
42 typedef T value_type;
43 bool operator()(T oldv, T newv) const throw() { return (newv < oldv); }
46 template<typename T>
47 struct search_le
49 typedef T value_type;
50 bool operator()(T oldv, T newv) const throw() { return (newv <= oldv); }
53 template<typename T>
54 struct search_eq
56 typedef T value_type;
57 bool operator()(T oldv, T newv) const throw() { return (newv == oldv); }
60 template<typename T>
61 struct search_ne
63 typedef T value_type;
64 bool operator()(T oldv, T newv) const throw() { return (newv != oldv); }
67 template<typename T>
68 struct search_ge
70 typedef T value_type;
71 bool operator()(T oldv, T newv) const throw() { return (newv >= oldv); }
74 template<typename T>
75 struct search_gt
77 typedef T value_type;
78 bool operator()(T oldv, T newv) const throw() { return (newv > oldv); }
81 template<typename T>
82 struct search_seqlt
84 typedef T value_type;
85 bool operator()(T oldv, T newv) const throw()
87 T mask = (T)1 << (sizeof(T) * 8 - 1);
88 T diff = newv - oldv;
89 return ((diff & mask) != (T)0);
93 template<typename T>
94 struct search_seqle
96 typedef T value_type;
97 bool operator()(T oldv, T newv) const throw()
99 T mask = (T)1 << (sizeof(T) * 8 - 1);
100 T diff = newv - oldv;
101 return ((diff & mask) != (T)0) || (diff == (T)0);
105 template<typename T>
106 struct search_seqge
108 typedef T value_type;
109 bool operator()(T oldv, T newv) const throw()
111 T mask = (T)1 << (sizeof(T) * 8 - 1);
112 T diff = newv - oldv;
113 return ((diff & mask) == (T)0);
117 template<typename T>
118 struct search_seqgt
120 typedef T value_type;
121 bool operator()(T oldv, T newv) const throw()
123 T mask = (T)1 << (sizeof(T) * 8 - 1);
124 T diff = newv - oldv;
125 return ((diff & mask) == (T)0) && (diff != (T)0);
130 template<typename T>
131 struct search_value_helper
133 typedef typename T::value_type value_type;
134 search_value_helper(const T& v) throw()
135 : val(v)
138 bool operator()(const uint8_t* newv, const uint8_t* oldv, uint64_t left, int endian) const throw()
140 if(left < sizeof(value_type))
141 return false;
142 value_type v1 = serialization::read_endian<value_type>(oldv, endian);
143 value_type v2 = serialization::read_endian<value_type>(newv, endian);
144 return val(v1, v2);
146 const T& val;
149 namespace
151 void dq_all_after(uint64_t* still_in, uint64_t& candidates, uint64_t size, uint64_t after)
153 for(uint64_t i = after; i < size; i++) {
154 if((still_in[i / 64] >> (i % 64)) & 1) {
155 still_in[i / 64] &= ~(1ULL << (i % 64));
156 candidates--;
161 inline void dq_entry(uint64_t* still_in, uint64_t& candidates, uint64_t i)
163 if((still_in[i / 64] >> (i % 64)) & 1) {
164 still_in[i / 64] &= ~(1ULL << (i % 64));
165 candidates--;
169 inline uint64_t next_multiple_of_64(uint64_t i)
171 return (i + 64) >> 6 << 6;
174 inline uint64_t prev_multiple_of_64(uint64_t i)
176 return ((i - 64) >> 6 << 6) + 63;
179 template<typename T>
180 void search_block_mapped(uint64_t* still_in, uint64_t& candidates, memory_region& region, uint64_t rbase,
181 uint64_t ibase, T& helper, std::vector<uint8_t>& previous_content)
183 if(ibase >= previous_content.size())
184 return;
185 unsigned char* mem = region.direct_map;
186 uint64_t rsize = region.size;
187 int endian = region.endian;
188 uint64_t i = ibase;
189 uint64_t switch_at = ibase + rsize - rbase; //The smallest i not in this region.
190 rsize = min(rsize, previous_content.size() - ibase);
191 for(unsigned j = rbase; j < rsize; i++, j++) {
192 //Advance blocks of 64 addresses if none of the addresses match.
193 while(still_in[i / 64] == 0 && next_multiple_of_64(i) <= switch_at) {
194 uint64_t old_i = i;
195 i = next_multiple_of_64(i);
196 j += i - old_i;
198 //This might match. Check it.
199 if(!helper(mem + j, &previous_content[i], rsize - j, endian))
200 dq_entry(still_in, candidates, i);
204 template<typename T>
205 void search_block_read(uint64_t* still_in, uint64_t& candidates, memory_region& region, uint64_t rbase,
206 uint64_t ibase, T& helper, std::vector<uint8_t>& previous_content)
208 if(ibase >= previous_content.size())
209 return;
210 uint64_t rsize = region.size;
211 int endian = region.endian;
212 uint64_t i = ibase;
213 uint64_t switch_at = ibase + rsize - rbase; //The smallest i not in this region.
215 //The buffer.
216 const size_t buffer_capacity = 4096;
217 unsigned char buffer[buffer_capacity];
218 uint64_t buffer_offset = 0; //The offset in buffer.
219 uint64_t buffer_remaining = 0; //Number of bytes remaining in buffer.
220 uint64_t buffer_soffset = rbase; //The offset buffer start corresponds to.
221 uint64_t buffer_eoffset = rbase; //The first offset not in buffer.
223 rsize = min(rsize, previous_content.size() - ibase);
224 for(unsigned j = rbase; j < rsize; i++, j++) {
225 //Fill the buffer again if it has gotten low enough.
226 if(buffer_remaining < 256 && buffer_eoffset != rsize) {
227 if(buffer_remaining)
228 memmove(buffer, buffer + buffer_offset, buffer_remaining);
229 buffer_offset = 0;
230 size_t fill_amount = min((uint64_t)buffer_capacity, rsize - buffer_eoffset);
231 region.read(buffer_eoffset, buffer + buffer_remaining, fill_amount);
232 buffer_eoffset += fill_amount;
233 buffer_remaining += fill_amount;
235 //Advance blocks of 64 addresses if none of the addresses match.
236 while(still_in[i / 64] == 0 && next_multiple_of_64(i) <= switch_at) {
237 uint64_t old_i = i;
238 i = next_multiple_of_64(i);
239 uint64_t advance = i - old_i;
240 j += advance;
241 buffer_offset += advance;
242 buffer_remaining -= advance;
243 buffer_soffset += advance;
245 //This might match. Check it.
246 if(!helper(buffer + buffer_offset, &previous_content[i], buffer_remaining, endian))
247 dq_entry(still_in, candidates, i);
248 buffer_offset++;
249 buffer_remaining--;
250 buffer_soffset++;
254 void copy_block_mapped(uint8_t* old, memory_region& region, uint64_t rbase, uint64_t maxr)
256 memcpy(old, region.direct_map + rbase, min(region.size - rbase, maxr));
259 void copy_block_read(uint8_t* old, memory_region& region, uint64_t rbase, uint64_t maxr)
261 region.read(rbase, old, min(region.size - rbase, maxr));
264 void dq_block(uint64_t* still_in, uint64_t& candidates, memory_region& region, uint64_t rbase, uint64_t ibase,
265 uint64_t first, uint64_t last, uint64_t ramsize)
267 if(ibase >= ramsize)
268 return;
269 if(last < region.base + rbase || first >= region.base + region.size)
270 return; //Nothing to DQ in this block.
271 first = max(first, region.base + rbase) - region.base + rbase;
272 last = last - region.base - rbase;
273 last = min(last, ramsize - ibase);
274 uint64_t ilast = ibase + last;
275 for(uint64_t i = ibase + first; i <= ilast; i++) {
276 while(still_in[i / 64] == 0 && next_multiple_of_64(i) <= ilast) {
277 i = next_multiple_of_64(i);
278 continue;
280 dq_entry(still_in, candidates, i);
284 void candidates_block(std::list<uint64_t>& out, memory_region& region, uint64_t rbase, uint64_t ibase,
285 uint64_t* still_in, uint64_t ramsize)
287 if(ibase >= ramsize)
288 return;
289 uint64_t rsize = region.size;
290 uint64_t i = ibase;
291 uint64_t switch_at = ibase + rsize - rbase; //The smallest i not in this region.
292 rsize = min(rsize, ramsize - ibase);
293 for(unsigned j = rbase; j < rsize; i++, j++) {
294 //Advance blocks of 64 addresses if none of the addresses match.
295 while(still_in[i / 64] == 0 && next_multiple_of_64(i) <= switch_at) {
296 uint64_t old_i = i;
297 i = next_multiple_of_64(i);
298 j += i - old_i;
300 if((still_in[i / 64] >> (i % 64)) & 1)
301 out.push_back(region.base + j);
307 void memory_search::dq_range(uint64_t first, uint64_t last)
309 auto t = mspace.lookup_linear(0);
310 if(!t.first)
311 return;
312 uint64_t i = 0;
313 uint64_t size = previous_content.size();
314 while(true) {
315 //Switch blocks.
316 t = mspace.lookup_linear(i);
317 if(!t.first) {
318 //DQ all rest.
319 dq_all_after(&still_in[0], candidates, size, i);
320 break;
322 dq_block(&still_in[0], candidates, *t.first, t.second, i, first, last, previous_content.size());
323 i += t.first->size - t.second;
327 template<class T> void memory_search::search(const T& obj) throw()
329 search_value_helper<T> helper(obj);
330 auto t = mspace.lookup_linear(0);
331 if(!t.first)
332 return;
333 uint64_t size = previous_content.size();
334 uint64_t i = 0;
335 while(true) {
336 //Switch blocks.
337 t = mspace.lookup_linear(i);
338 if(!t.first) {
339 //DQ all rest.
340 dq_all_after(&still_in[0], candidates, size, i);
341 break;
343 if(t.first->direct_map) {
344 search_block_mapped(&still_in[0], candidates, *t.first, t.second, i, helper, previous_content);
345 copy_block_mapped(&previous_content[i], *t.first, t.second,
346 max((uint64_t)previous_content.size(), i) - i);
347 } else {
348 search_block_read(&still_in[0], candidates, *t.first, t.second, i, helper, previous_content);
349 copy_block_read(&previous_content[i], *t.first, t.second,
350 max((uint64_t)previous_content.size(), i) - i);
352 i += t.first->size - t.second;
356 template<typename T> void memory_search::s_value(T value) throw() { search(search_value<T>(value)); }
357 template<typename T> void memory_search::s_difference(T value) throw() { search(search_difference<T>(value)); }
358 template<typename T> void memory_search::s_lt() throw() { search(search_lt<T>()); }
359 template<typename T> void memory_search::s_le() throw() { search(search_le<T>()); }
360 template<typename T> void memory_search::s_eq() throw() { search(search_eq<T>()); }
361 template<typename T> void memory_search::s_ne() throw() { search(search_ne<T>()); }
362 template<typename T> void memory_search::s_ge() throw() { search(search_ge<T>()); }
363 template<typename T> void memory_search::s_gt() throw() { search(search_gt<T>()); }
364 template<typename T> void memory_search::s_seqlt() throw() { search(search_seqlt<T>()); }
365 template<typename T> void memory_search::s_seqle() throw() { search(search_seqle<T>()); }
366 template<typename T> void memory_search::s_seqge() throw() { search(search_seqge<T>()); }
367 template<typename T> void memory_search::s_seqgt() throw() { search(search_seqgt<T>()); }
369 template<typename T> T memory_search::v_read(uint64_t addr) throw() { return mspace.read<T>(addr); }
370 template<typename T> void memory_search::v_write(uint64_t addr, T val) throw() { mspace.write<T>(addr, val); }
372 template<typename T> T memory_search::v_readold(uint64_t addr) throw()
374 uint64_t i = 0;
375 auto t = mspace.lookup_linear(0);
376 if(!t.first)
377 return 0;
378 //Search for linear range containing the address.
379 while(true) {
380 t = mspace.lookup_linear(i);
381 if(!t.first)
382 return 0;
383 if(t.first->base <= addr && t.first->base + t.first->size > addr) {
384 //Global address t.first->base <=> linear address i.
385 uint64_t linaddr = addr - t.first->base + i;
386 uint64_t maxr = t.first->size + t.first->base - addr;
387 maxr = min(maxr, (uint64_t)sizeof(T));
388 char buf[sizeof(T)] = {0};
389 if(previous_content.size() < linaddr + maxr)
390 return 0;
391 memcpy(buf, &previous_content[linaddr], maxr);
392 return serialization::read_endian<T>(buf, t.first->endian);
394 i += t.first->size - t.second;
396 return 0;
399 template<typename T> void memorysearch_pull_type(memory_search& s)
401 T val;
402 eat_argument(&memory_search::s_value<T>);
403 eat_argument(&memory_search::s_difference<T>);
404 eat_argument(&memory_search::s_lt<T>);
405 eat_argument(&memory_search::s_le<T>);
406 eat_argument(&memory_search::s_eq<T>);
407 eat_argument(&memory_search::s_ne<T>);
408 eat_argument(&memory_search::s_ge<T>);
409 eat_argument(&memory_search::s_gt<T>);
410 eat_argument(&memory_search::s_seqlt<T>);
411 eat_argument(&memory_search::s_seqle<T>);
412 eat_argument(&memory_search::s_seqge<T>);
413 eat_argument(&memory_search::s_seqgt<T>);
414 eat_argument(&memory_search::v_read<T>);
415 eat_argument(&memory_search::v_readold<T>);
416 eat_argument(&memory_search::v_write<T>);
419 template<typename T> void memorysearch_pull_type2(memory_search& s)
421 T val;
422 eat_argument(&memory_search::s_value<T>);
423 eat_argument(&memory_search::s_difference<T>);
424 eat_argument(&memory_search::s_lt<T>);
425 eat_argument(&memory_search::s_le<T>);
426 eat_argument(&memory_search::s_eq<T>);
427 eat_argument(&memory_search::s_ne<T>);
428 eat_argument(&memory_search::s_ge<T>);
429 eat_argument(&memory_search::s_gt<T>);
430 eat_argument(&memory_search::v_read<T>);
431 eat_argument(&memory_search::v_readold<T>);
432 eat_argument(&memory_search::v_write<T>);
435 void memorysearch_pull_all(memory_search& s)
437 memorysearch_pull_type<int8_t>(s);
438 memorysearch_pull_type<uint8_t>(s);
439 memorysearch_pull_type<int16_t>(s);
440 memorysearch_pull_type<uint16_t>(s);
441 memorysearch_pull_type<ss_int24_t>(s);
442 memorysearch_pull_type<ss_uint24_t>(s);
443 memorysearch_pull_type<int32_t>(s);
444 memorysearch_pull_type<uint32_t>(s);
445 memorysearch_pull_type<int64_t>(s);
446 memorysearch_pull_type<uint64_t>(s);
447 memorysearch_pull_type2<float>(s);
448 memorysearch_pull_type2<double>(s);
451 void memory_search::update() throw() { search(search_update()); }
453 uint64_t memory_search::get_candidate_count() throw()
455 return candidates;
458 std::list<uint64_t> memory_search::get_candidates() throw(std::bad_alloc)
460 std::list<uint64_t> out;
461 auto t = mspace.lookup_linear(0);
462 if(!t.first)
463 return out;
464 uint64_t i = 0;
465 while(true) {
466 //Switch blocks.
467 t = mspace.lookup_linear(i);
468 if(!t.first)
469 return out;
470 candidates_block(out, *t.first, t.second, i, &still_in[0], previous_content.size());
471 i += t.first->size - t.second;
473 return out;
476 bool memory_search::is_candidate(uint64_t addr) throw()
478 auto t = mspace.lookup_linear(0);
479 if(!t.first)
480 return false;
481 uint64_t i = 0;
482 while(true) {
483 //Switch blocks.
484 t = mspace.lookup_linear(i);
485 if(!t.first)
486 return false;
487 if(i >= previous_content.size())
488 return false;
489 uint64_t rsize = t.first->size;
490 uint64_t switch_at = i + rsize - t.second; //The smallest i not in this region.
491 rsize = min(rsize, previous_content.size() - i);
492 if(addr >= t.first->base + t.second && addr < t.first->base + rsize) {
493 uint64_t adv = addr - (t.first->base + t.second);
494 uint64_t ix = i + adv;
495 return ((still_in[ix / 64] >> (ix % 64)) & 1);
497 i += t.first->size - t.second;
499 return false;
502 uint64_t memory_search::cycle_candidate_vma(uint64_t addr, bool next) throw()
504 auto t = mspace.lookup_linear(0);
505 if(!t.first)
506 return false;
507 uint64_t i = 0;
508 while(true) {
509 //Switch blocks.
510 t = mspace.lookup_linear(i);
511 if(!t.first)
512 return addr;
513 if(i >= previous_content.size())
514 return addr;
515 uint64_t rsize = t.first->size;
516 uint64_t switch_at = i + rsize - t.second; //The smallest i not in this region.
517 rsize = min(rsize, previous_content.size() - i);
518 if(addr >= t.first->base + t.second && addr < t.first->base + rsize) {
519 uint64_t baseaddr = t.first->base + t.second;
520 int64_t tryoff = addr - baseaddr + i;
521 uint64_t finoff = tryoff;
522 uint64_t warp = i;
523 bool warped = false;
524 if(next) {
525 //Cycle forwards.
526 tryoff++;
527 while(tryoff < finoff || !warped) {
528 if(tryoff >= switch_at) {
529 tryoff = warp;
530 if(warped)
531 return addr;
532 warped = true;
534 if(still_in[tryoff / 64] == 0)
535 tryoff = next_multiple_of_64(tryoff);
536 else {
537 if((still_in[tryoff / 64] >> (tryoff % 64)) & 1)
538 return tryoff - i + baseaddr;
539 tryoff++;
542 } else {
543 //Cycle backwards.
544 tryoff--;
545 while(tryoff > finoff || !warped) {
546 if(tryoff < warp) {
547 tryoff = switch_at - 1;
548 if(warped)
549 return addr;
550 warped = true;
552 if(still_in[tryoff / 64] == 0)
553 tryoff = prev_multiple_of_64(tryoff);
554 else {
555 if((still_in[tryoff / 64] >> (tryoff % 64)) & 1)
556 return tryoff - i + baseaddr;
557 tryoff--;
562 i += t.first->size - t.second;
564 return addr;
567 void memory_search::reset() throw(std::bad_alloc)
569 uint64_t linearram = mspace.get_linear_size();
570 previous_content.resize(linearram);
571 still_in.resize((linearram + 63) / 64);
572 for(uint64_t i = 0; i < linearram / 64; i++)
573 still_in[i] = 0xFFFFFFFFFFFFFFFFULL;
574 if(linearram % 64)
575 still_in[linearram / 64] = (1ULL << (linearram % 64)) - 1;
576 candidates = linearram;
578 auto t = mspace.lookup_linear(0);
579 if(!t.first)
580 return;
581 uint64_t i = 0;
582 while(true) {
583 //Switch blocks.
584 t = mspace.lookup_linear(i);
585 if(!t.first)
586 break;
587 if(t.first->direct_map)
588 copy_block_mapped(&previous_content[i], *t.first, t.second,
589 max((uint64_t)previous_content.size(), i) - i);
590 else
591 copy_block_read(&previous_content[i], *t.first, t.second,
592 max((uint64_t)previous_content.size(), i) - i);
593 i += t.first->size - t.second;
598 void memory_search::savestate(std::vector<char>& buffer, enum savestate_type type) const
600 size_t size;
601 uint64_t linsize = mspace.get_linear_size();
602 if(type == ST_PREVMEM)
603 size = 9 + linsize;
604 else if(type == ST_SET)
605 size = 17 + (linsize + 63) / 64 * 8;
606 else if(type == ST_ALL)
607 size = 17 + linsize + (linsize + 63) / 64 * 8;
608 else
609 throw std::runtime_error("Invalid savestate type");
610 buffer.resize(size);
611 buffer[0] = type;
612 serialization::u64b(&buffer[1], linsize);
613 size_t offset = 9;
614 if(type == ST_PREVMEM || type == ST_ALL) {
615 memcpy(&buffer[offset], &previous_content[0], min(linsize, (uint64_t)previous_content.size()));
616 offset += linsize;
618 if(type == ST_SET || type == ST_ALL) {
619 serialization::u64b(&buffer[offset], candidates);
620 offset += 8;
621 size_t bound = min((linsize + 63) / 64, (uint64_t)still_in.size());
622 for(unsigned i = 0; i < bound; i++) {
623 serialization::u64b(&buffer[offset], still_in[i]);
624 offset += 8;
629 void memory_search::loadstate(const std::vector<char>& buffer)
631 if(buffer.size() < 9 || buffer[0] < ST_PREVMEM || buffer[0] > ST_ALL)
632 throw std::runtime_error("Invalid memory search save");
633 uint64_t linsize = serialization::u64b(&buffer[1]);
634 if(linsize != mspace.get_linear_size())
635 throw std::runtime_error("Save size mismatch (not from this game)");
636 if(!previous_content.size())
637 reset();
638 savestate_type type = (savestate_type)buffer[0];
639 size_t offset = 9;
640 if(type == ST_PREVMEM || type == ST_ALL) {
641 memcpy(&previous_content[0], &buffer[offset], min(linsize, (uint64_t)previous_content.size()));
642 offset += linsize;
644 if(type == ST_SET || type == ST_ALL) {
645 candidates = serialization::u64b(&buffer[offset]);
646 offset += 8;
647 size_t bound = min((linsize + 63) / 64, (uint64_t)still_in.size());
648 for(unsigned i = 0; i < bound; i++) {
649 still_in[i] = serialization::u64b(&buffer[offset]);
650 offset += 8;