blob: 9451b3dc8593b45a6bab2ed8f8df5266f0bc0c68 [file] [view]
# Differences between Dart compilers
The various Dart compilers and compilation modes have different optimizations.
This can lead to differences in the recorded API usage between compilers.
## Compiler Optimizations
The following compiler optimizations can influence the recorded API usage:
* **Tree-shaking / Dead code elimination:** Code that is not used is removed
from the compiled output. If an API is only used in code that is
tree-shaken, the API usage will not be recorded. The compilers have
different levels of sophistication in their tree-shaking algorithms. For
example, `dart compile exe` might tree-shake a call, while `dart2js` might
not.
* **Constant propagation and inlining:** Constant propagation and inlining of
other code might lead to record-use annotated API calls receiving more
constant arguments or being promoted to static calls. These optimizations
might also lead to tearoffs being promoted to static calls or const values
showing up for call sites that were propagated there by the compiler. (The
record-use annotated API itself should not be optimized away by inlining
or constant propagation. The compilers preserve such APIs.)
* **Other optimizations:** Other optimizations that can affect the recorded
API usage include type inference and propagation, loop optimizations, and
`late` variable initialization.
## Loading Units and Code Partitioning
When deferred loading is used, compilers partition the application into
separate loading units (e.g., separate JavaScript files or AOT loading units)
that can be loaded dynamically.
How different compilers partition code and name these loading units can vary
significantly:
* **Partitioning strategies:** The algorithms and heuristics for grouping code
into deferred loading units differ between compilers (such as VM AOT,
dart2js, and dart2wasm). Consequently, a particular API usage might end up
associated with different loading units depending on the compiler.
* **Loading unit names:** Compilers use different naming schemes or
identifiers for loading units (e.g., integers like `1` or `2`, or file names
like `out.js_1.part.js`).
## Recommendations for Accurate Recording with `package:record_use`
The `package:record_use` library records API usage by analyzing the compiled
output of a program. This means that the recorded API usage can vary depending
on the compiler and compilation mode that is used. To ensure more accurate and
consistent recording, consider the following recommendations:
* **`@mustBeConst` annotation:** When designing APIs that are intended for
analysis by `package:record_use`, consider using the `@mustBeConst`
annotation on parameters. This annotation forces call sites to provide
*obviously constant* arguments, which compilers are more likely to treat
consistently across different optimization levels.
* **Warning for non-constant or dynamic calls:** To improve the accuracy and
reliability of `package:record_use` reports, consider adding a warning
mechanism within the tool's analysis (e.g., in a "link hook"). This warning
would trigger if any API calls are found with non-constant arguments,
dynamic calls, or tear-offs. These constructs can lead to inconsistent
reporting across compilers due to varying optimization strategies.
* **Aligning loading unit identifiers:** When writing tests or verification
logic that compares recordings across different compilers or compilation
configurations, use the `loadingUnitMapping` parameter in `semanticEquals`
to map/normalize the compiler-specific loading unit identifiers to a
unified, consistent format.