By Luyuan, from Xianyu Technology Team
The Xianyu Technology Team needs to connect FaaS code with Flutter business code to achieve the best development experience during unifying Flutter projects. This way, the same code deployed in FaaS can also be directly applied to the main project of the business code, achieving the real project unification. To this end, we implement code decoupling for FaaS and Flutter code through the Remote Procedure Call (RPC), while project decoupling relies on the Tree Shaking mechanism of Flutter and Dart during compilation. We need to understand how Tree Shaking works to avoid potential problems. This article explores the Tree Shaking mechanism during the compilation process using the Flutter Engine source code.
Tree Shaking is also used in Flutter to reduce the final packet size. Flutter provides three construction modes. For each mode, the Flutter compiler has different optimizations on the output binary files. The Tree Shaking mechanism can't be triggered in the Debug mode. Among the Ahead of Time (AOT) products compiled in Profile or Release mode, several important products intuitively show that the Tree Shaking mechanism is working.
Here is a simple code example:
The code is very simple, containing an unused method
_unused. Next, we compile the code in Profile mode, and view the final compiled product through DevTools, which is shown in the following figure:
_unused method is not contained in functions, which means this part of the unneeded code is eliminated during the compilation process. In addition to functions, lib and imported dart files undergo similar Tree Shaking processing during Flutter compilation. The section below explains how it works.
This sequence diagram of the "flutter run" command execution was designed by an experienced developer called Gityuan. In this diagram, the entire compilation process is relatively long because the binary executable file
gen_snapshot is called by
GenSnapshot.run() to generate machine codes. The corresponding source code is in the director of
The following figure shows the execution process inside
The Tree Shaking mechanism works in the compilation stage using the
CompileAll() method. The following section explores how the Flutter compiler truncates the code. You may make a comparison query of the source code. The path is
The preparation is necessary, which requires the object pool to be retained until the AOT compilation is completed. Thus, a long-standing handle is needed. It uses StackZone.
The class hierarchy needs to be stable before compilation to use Class Hierarchy Analysis (CHA). At the same time, functions must remain, even when the class of functions is not finalized in entry point finding. CHA is the optimization on the compiler. It can change virtual calls to direct calls based on the analysis of class hierarchies.
AOT constructors and compute optimization instructions can be used in inline functions:
The next step is to generate a stub code. First, use
StubCode::InterpretCall to obtain the object pool. Then, results are stored in
object_store and obtained through several methods, including
StubCode::Build. Next, the method names of dynamic functions are collected and then added to the beginnings of C++ allocation and calls as roots through
AddRoots(). At the same time, all objects with @ pragma ('vm:entry-point') are also added as roots through
After that, the code starts to compile.
Iterate() is the core of compilation. In the example below, the previously found root is used as the target to traverse all callers that have added the target.
The main call chain of this method is listed below:
ProcessFunction ==> CompileFunction ==> PrecompileFunctionHelper ==> PrecompileParsedFunctionHelper.Compile
The compilation is now completed. Useless code is eliminated in the Tree Shaking stage.
During the compilation process above, the call information, such as function and class, has been output. According to the information, the compiler can distinguish unnecessary code. The section below uses Function processing as an example:
In this method, after obtaining handles like Class and Library, the code in each packet is processed with the units of libraries. Functions of all classes are traversed for processing.
AddTypesOf(const Function& function), the called function is added to the
functions_to_retain_ pool. At the same time, type parameters in the Function are read and added to the corresponding
types_to_retain_ pools through the
AddType method, and similarly for the
DropTypeParameters. These parameters are used for type information in the Tree Shaking operation.
Class information is processed by method
AddTypesOf(const Class& cls). The process is similar to the process of functions, so it will not be introduced here.
In this method, entries for serializing the scheduling table are created before the Drop method is executed because the compiler may clear references to Code objects. At the same time, the scheduling table builder is deleted to ensure that no new entries are added afterward.
In this method, the call entries of static functions are replaced by the anonymous internal class
Next, a series of Drop methods are performed. These methods remove redundant methods, fields, classes, and libraries. They are shown below:
The specific call sequence is shown in the following figure:
Since the internal implementation ideas of these methods are similar, we used
DropFunctions method as an example.
The major part of this method is the
functions_to_retain_ pool mentioned above. It determines whether a function has a root caller. If the function object is not contained in the pool, the function can be eliminated. Then, the remaining functions are written back to the class, and the call table of the class is updated.
drop_function declared in the method is used to eliminate the function.
Then, all the functions in the code are traversed, and the useless function code is marked and deleted by the
Functions that need to be retained are written into the class.
A new call table of the class is generated, and unnecessary functions that may exist in the call table are deleted.
The last step is the processing of some edge cases, such as inline functions, which are not described in detail in this article. After the Drop stage is completed, the code that can be eliminated is put into the deletion pool. Then, during the end stage of compilation, the size of binary files is further reduced.
The compilation enters the end stage after Tree Shaking is completed. The process includes code obfuscation and garbage collection.
The key code of the Dedup method is listed below:
A lot of repeated data deletion works are done within this method. In AOT mode, binder runs after Tree Shaking while all targets have been compiled. Therefore, binder replaces all static calls with direct calls of targets, which further reduces the binary file size. At this moment, all compilation works have been completed, and Tree Shaking has completed the mission.
In Flutter 1.20, the unused icon fonts in the project can be removed with the Tree Shaking mechanism to reduce the packet size to around 100 KB. However, this method is implemented in the
build_system, not in the compilation stage described above. In the
build_system, this method optimizes assets. For related physical records (PR), please see this page.
This article explores the Tree Shaking mechanism during the compilation process using the Flutter Engine source code. This mechanism provides a theoretical basis for project decoupling, simplifies the implementation of project unification, and inspires us to reduce the packet size.
Exploring Memory Leaks in Flutter from the Rendering Process
An In-Depth Understanding of Flutter Compilation Principles and Optimizations
56 posts | 4 followersFollow
XianYu Tech - May 13, 2021
Alibaba Tech - July 11, 2019
XianYu Tech - September 8, 2020
Alibaba Clouder - February 8, 2021
XianYu Tech - September 4, 2020
HaydenLiu - December 5, 2022
56 posts | 4 followersFollow
Explore Web Hosting solutions that can power your personal website or empower your online business.Learn More
A low-code development platform to make work easierLearn More
Help enterprises build high-quality, stable mobile appsLearn More
Explore how our Web Hosting solutions help small and medium sized companies power their websites and online businesses.Learn More
More Posts by XianYu Tech