Skip to content

Instantly share code, notes, and snippets.

@eernstg
Last active August 1, 2022 12:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eernstg/bc60f4a3f9926aa485fc2676af8814f7 to your computer and use it in GitHub Desktop.
Save eernstg/bc60f4a3f9926aa485fc2676af8814f7 to your computer and use it in GitHub Desktop.
Illustrate how link-time sets or maps could be used to reduce the size of conditions based on tree-shaking
// Some code in this file depends on the 'link-time sets and maps' feature,
// cf. https://github.com/dart-lang/language/issues/371. The basic idea is that
// a link-time set/map can be declared in one library L, and any library L1 that
// imports L can contain contributions to said set/map (you can do `add` on a
// `Set` and `[]=` on a `Map`), and those operations occur before run time.
// For each of the contributions (`add` or `[]=` on a link time collection), the
// operation can be made conditional on the existence of some other entity (or,
// in general, on a boolean expression whose basic predicates is those entities).
// For instance, we could have `myLinkTimeSet.add(5) if C;`, which means that `5`
// will be added to `myLinkTimeSet` if and only if the class `C` is still contained
// in the program after tree shaking. A typical usage could be something like
// `myFactories[C] = C.new if C;` which would add a mapping from the type literal
// `C` to the factory function `C.new` if and only if the program contains `C`.
// --- Original code, relying on many tests of the form `identical(_, T)` ---
dynamic injectorGetInternal(
dynamic token, int nodeIndex, dynamic notFoundResult) {
if ((2 == nodeIndex)) {
if (identical(token, import8.DeferredValidator)) {
return this._DeferredValidator_2_5;
}
if (((((identical(token, import9.MaterialInputComponent) ||
identical(token, import21.BaseMaterialInput)) ||
identical(token, import22.ReferenceDirective)) ||
identical(token, import23.Focusable)) ||
identical(token, import24.HasDisabled))) {
return this._MaterialInputComponent_2_6;
}
if (identical(token, const import6.MultiToken<Object>('NgValidators'))) {
return this._NgValidators_2_9;
}
}
if (((4 <= nodeIndex) && (nodeIndex <= 5))) {
if (identical(token, import28.AcxDarkTheme)) {
return this._AcxDarkTheme_4_5;
}
if ((((identical(token, import12.MaterialButtonComponent) ||
identical(token, import29.ButtonDirective)) ||
identical(token, import24.HasDisabled)) ||
identical(token, import23.Focusable))) {
return this._MaterialButtonComponent_4_6;
}
}
return notFoundResult;
}
// --- Code using link-time sets ---
// If the `identical` based expressions are reused several times,
// it may be better to create link-time sets. We could then eliminate
// dead code by detecting that some of these sets are empty.
// Declare this in any library L, and import L from locations where
// elements are added to this set, and where the set is used. We
// might want to use `Set<Object>`, I just kept a top type to minimize
// the number of adjustments to the method below.
Set<Object?> myInjectorGetInternal_2_5Set of const;
Set<Object?> myInjectorGetInternal_2_6Set of const;
Set<Object?> myInjectorGetInternal_2_RestSet of const;
Set<Object?> myInjectorGetInternal_4_5Set of const;
Set<Object?> myInjectorGetInternal_4_RestSet of const;
// Add elements to the sets based on tree shaking: The addition is
// performed if and only iff tree shaking detects a usage of the entity
// in the `if` clause. Declare this in a library where the sets above
// are available.
const myInjectorGetInternal_2_5Set.add(import8.DeferredValidator)
if import8.DeferredValidator;
const myInjectorGetInternal_2_6Set.add(import9.MaterialInputComponent)
if import9.MaterialInputComponent;
const myInjectorGetInternal_2_6Set.add(import21.BaseMaterialInput)
if import21.BaseMaterialInput;
const myInjectorGetInternal_2_6Set.add(import22.ReferenceDirective)
if import22.ReferenceDirective;
const myInjectorGetInternal_2_6Set.add(import23.Focusable)
if import23.Focusable;
const myInjectorGetInternal_2_6Set.add(import24.HasDisabled)
if import24.HasDisabled;
// We could specify the set 'NgValidators' to make this more precise.
const myInjectorGetInternal_2_RestSet.add(
const import6.MultiToken<Object>('NgValidators'));
// ... and similarly for the other sets.
// Declare this in any library that imports the above link-time sets
// (it is not necessary to have the `add` declarations in scope).
dynamic injectorGetInternal(
dynamic token, int nodeIndex, dynamic notFoundResult) {
if ((2 == nodeIndex)) {
// For each link-time set, we may detect at link time that it is empty,
// and remove the whole `if` statement as dead code.
if (myInjectorGetInternal_2_5Set.contains(token)) {
return this._DeferredValidator_2_5;
}
if (myInjectorGetInternal_2_6Set.contains(token)) {
return this._MaterialInputComponent_2_6;
}
if (myInjectorGetInternal_2_RestSet.contains(token)) {
return this._NgValidators_2_9;
}
}
if (((4 <= nodeIndex) && (nodeIndex <= 5))) {
if (myInjectorGetInternal_4_5Set.contains(token)) {
return this._AcxDarkTheme_4_5;
}
if (myInjectorGetInternal_4_RestSet.contains(token)) {
return this._MaterialButtonComponent_4_6;
}
}
return notFoundResult;
}
// --- Code using link-time maps ---
// It might be a useful approach to use link-time maps, mapping
// the tokens to whatever `this._DeferredValidator_2_5` is, if
// that can be expressed as a constant expression. So we'll assume
// that this is indeed possible, and use names like
// `_constant_DeferredValidator_2_5` to stand for that constant
// below. I'm also assuming that these objects will be factory
// functions, hence the value type `Function` below. Any other type
// can be used if needed, of course.
// Declare this in any library L, and import L from locations where
// elements are added to this set, and where the set is used. We might
// want to use `Map<Object, ...>`, I just kept a top type to minimize
// the number of adjustments to the method below.
Map<Object?, Function> myInjectorGetInternal_2_5Map of const;
Map<Object?, Function> myInjectorGetInternal_2_6Map of const;
Map<Object?, Function> myInjectorGetInternal_2_RestMap of const;
Map<Object?, Function> myInjectorGetInternal_4_5Map of const;
Map<Object?, Function> myInjectorGetInternal_4_RestMap of const;
// Add elements to the sets based on tree shaking: The addition is
// performed if and only iff tree shaking detects a usage of the entity
// in the `if` clause. Declare this in a library where the sets above
// are available.
const myInjectorGetInternal_2Map[import8.DeferredValidator] =
_constant_DeferredValidator_2_5 if import8.DeferredValidator;
const myInjectorGetInternal_2Map[import9.MaterialInputComponent] =
_constant_MaterialInputComponent_2_6 if import9.MaterialInputComponent;
const myInjectorGetInternal_2Map[import21.BaseMaterialInput] =
_constant_MaterialInputComponent_2_6 if import21.BaseMaterialInput;
const myInjectorGetInternal_2Map[import22.ReferenceDirective] =
_constant_MaterialInputComponent_2_6 if import22.ReferenceDirective;
const myInjectorGetInternal_2Map[import23.Focusable] =
_constant_MaterialInputComponent_2_6 if import23.Focusable;
const myInjectorGetInternal_2Map[import24.HasDisabled] =
_constant_MaterialInputComponent_2_6 if import24.HasDisabled;
const myInjectorGetInternal_2_RestMap[const import6.MultiToken<Object>('NgValidators')] =
_constant_NgValidators_2_9 if const import6.MultiToken<Object>('NgValidators');
// ... and similarly for the other maps.
// Declare this in any library that imports the above link-time maps
// (it is not necessary to have the `[]=` declarations in scope).
dynamic injectorGetInternal(
dynamic token, int nodeIndex, dynamic notFoundResult) {
if ((2 == nodeIndex)) {
// For the link-time map, we may detect at link time that it is empty,
// and remove the whole enclosing `if` statement as dead code.
var result = myInjectorGetInternal_2Map[token];
if (result != null) return result;
}
if (((4 <= nodeIndex) && (nodeIndex <= 5))) {
var result = myInjectorGetInternal_4_5Map[token];
if (result != null) return result;
}
return notFoundResult;
}
@eernstg
Copy link
Author

eernstg commented Aug 1, 2022

A few extra remarks about the situation where an if clause on a link-time add or []= declaration will succeed:

First, references to entities in if clauses of link-time contribution declarations (like C in the declaration const mySet.add(e) if C;) do not count as usages during tree shaking. So when we decide whether or not we should add the value of the constant expression e to the set mySet, we will do it if and only if some other part of the program refers to C and is reachable.

For a constant variable (whose value would be an OpaqueToken in this case), 'being used' is determined in the obvious way: It is used iff any reachable expression evaluates that variable.

For a constant expression which is not a variable (like const import6.MultiToken<Object>('NgValidators')), the expression is being used iff the set of canonicalized constant expression values includes this value.

For a type literal, it is considered to be used if it is evaluated as an expression (so int is used if we have Type f() => int;) or if it is passed as an actual type argument in an instance creation (so int is used if we can reach <int>[]). But using a type literal as or in a type annotation does not count, because that usage does not require the runtime to contain a first class representation of the type (so List<int>? xs; does not cause int or List to be a type literal which is used).

We would need to sort out exactly how to deal with dynamic type checks. For instance, do we 'use' the class C if we have a variable v of type C and an assignment to v of an expression of type dynamic? Similarly, we would not attempt to track whether a parameterized type is used, so if we can reach <T>[] then the type List is used, no matter which information we have available about T.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment