Skip to content

Speed up -AAA on large snapshots without changing its results - #26

Merged
trufae merged 1 commit into
radareorg:mainfrom
fatalSec:perf/aaa-signature-scaling
Sep 18, 2026
Merged

trufae merged 1 commit into
radareorg:mainfrom
fatalSec:perf/aaa-signature-scaling

Conversation

@fatalSec

Copy link
Copy Markdown
Contributor

On a 37,863-function Dart 3.13 iOS app r2flutter -AAA took 19 minutes (1,136s).

Three costs grew with snapshot size:

  • read_mem cached a single 1 MB window. modern_load_rodata_strings alternates between a cluster alloc stream and the RO data image megabytes away, so each string refilled the window twice (a 1 MB read plus the 0xff fill r_io_bank_read_at does first). Keep four windows with LRU eviction. Move the cache behind a pointer so DartCtx copies (flutter_model_load, dart_recovery_model_load) share buffers together with their addresses instead of leaving the original with stale ones, and drop a window whose refill failed since the failed read already overwrote it. -f 12.4s -> 0.5s; -c/-i/-T/-S ~6s -> <0.25s.

  • afs stores prototypes through r_anal_save_parsed_type, which calls r_anal_remove_parsed_type for each declared type. For an existing name that sorts and scans the whole type database although r_type_del has already removed every key of a function type. Names repeat often: anonymous closures, and names the C parser cuts at ':' (dyn:call is stored as call). Parse the prototype with r_anal_cparse and r_type_del those names first so afs takes its early return. The signature pass went from 1,127s to 27s.

  • With anal.trycatch (the radare2 6.2.2 default) every af walks all flags twice for try..catch|filter. After -AAA creates its flags that is O(flags) per function. Disable it around r2flutter's af loops when no try.* flag exists, which cannot change the result.

-AAA now takes 36s on that app (35s -> 6s on a smaller 3.13 build).

On a 37,863-function Dart 3.13 iOS app `r2flutter -AAA` took 19 minutes
(1,136s). Three costs grew with snapshot size:

- read_mem cached a single 1 MB window. modern_load_rodata_strings
  alternates between a cluster alloc stream and the RO data image
  megabytes away, so each string refilled the window twice (a 1 MB read
  plus the 0xff fill r_io_bank_read_at does first). Keep four windows
  with LRU eviction. Move the cache behind a pointer so DartCtx copies
  (flutter_model_load, dart_recovery_model_load) share buffers together
  with their addresses instead of leaving the original with stale ones,
  and drop a window whose refill failed since the failed read already
  overwrote it. `-f` 12.4s -> 0.5s; -c/-i/-T/-S ~6s -> <0.25s.

- afs stores prototypes through r_anal_save_parsed_type, which calls
  r_anal_remove_parsed_type for each declared type. For an existing name
  that sorts and scans the whole type database although r_type_del has
  already removed every key of a function type. Names repeat often:
  anonymous closures, and names the C parser cuts at ':' (dyn:call is
  stored as `call`). Parse the prototype with r_anal_cparse and
  r_type_del those names first so afs takes its early return. The
  signature pass went from 1,127s to 27s.

- With anal.trycatch (the radare2 6.2.2 default) every af walks all flags
  twice for try.<addr>.catch|filter. After -AAA creates its flags that is
  O(flags) per function. Disable it around r2flutter's af loops when no
  try.* flag exists, which cannot change the result.

-AAA now takes 36s on that app (35s -> 6s on a smaller 3.13 build). A
dump of afl, afs @@f, f, CC and ax after -AAA is byte-identical to the
previous build on both apps (796,194 and 138,032 lines), and so is stderr.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmxPHTVwSkr2HTHJHQsord
@trufae
trufae merged commit 6793505 into radareorg:main Sep 18, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants