Exploring Flutter Tree Shaking Mechanism in the Engine
The article dissects Flutter’s tree‑shaking process within the engine—tracing the compilation pipeline from GenSnapshot through AOT phases, showing how unused methods and resources are identified, retained or discarded, and finally obfuscated, thereby enabling dead‑code elimination, smaller binaries, and seamless Flutter‑FaaS integration.
Background In the exploration of integrating Flutter projects with FaaS at Xianyu, the goal is seamless coupling so that a single codebase can be deployed on FaaS and also imported into the main Flutter application.
To achieve this, RPC decoupling is used, relying on Flutter/Dart's Tree‑Shaking during compilation to eliminate unused code. This article investigates how Tree‑Shaking works by examining Flutter Engine source.
Prerequisite Knowledge Tree Shaking is a dead‑code elimination technique originating from LISP, later used in Google Closure Tools, dart2js, and Flutter. It is active in Profile/Release modes, producing artifacts such as app.dill (Dart bytecode) and snapshot_blob.bin.d (compiled Dart files and dependencies).
Exploring the Mechanism
Minimal Demo A simple example containing an unused method _unused is compiled in Profile mode. The resulting binary shows that _unused is removed, confirming Tree‑Shaking.
Code Analysis The compilation flow starts with flutter run , invoking GenSnapshot.run() which calls the native gen_snapshot binary. Tree‑Shaking occurs in the CompileAll() phase of the AOT compiler.
Key source file: third_party/dart/runtime/vm/compiler/aot/precompiler.cc .
Compilation Phase Preparations include using StackZone to keep object pools alive, applying Class Hierarchy Analysis (CHA) to stabilize class structures, pre‑compiling constructors for inlining, and generating stub code via StubCode::InterpretCall and StubCode::Build . Roots are added through AddRoots() and AddAnnotatedRoots() .
The core iteration Iterate() traverses call graphs from these roots, leading to the Tree‑Shaking stage.
Tree‑Shaking Phase Functions are examined via TraceForRetainedFunctions() . Libraries, classes, and functions are added to retention pools ( functions_to_retain_ , typeargs_to_retain_ , etc.). Subsequent Drop* methods (e.g., DropFunctions() , DropFields() ) remove unreferenced entities. The DropFunctions example shows how functions not in the retention pool are discarded and the class call tables are rebuilt.
Finalization Phase After Tree‑Shaking, code obfuscation and garbage collection occur. The Dedup method eliminates duplicate data, and the binder replaces static calls with direct calls, further shrinking the binary.
Extension In Flutter 1.20, Tree‑Shaking also removes unused icon fonts, reducing package size by ~100 KB (see PR #49737).
Conclusion By tracing the Flutter Engine’s compilation pipeline, this article reveals how Tree‑Shaking eliminates dead code, enabling project decoupling and offering insights for binary size optimization.
Xianyu Technology
Official account of the Xianyu technology team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.