[dart2wasm] Introduce unresolved accesses & link phase
This reduces ACX Gallery's main module from 8.4 MB to 2.75 MB.
This CL adds new infrastructure to our compiler: We allow codegen to
emit unresolved instructions which will get patched up later on in a
final link phase.
We use this for constants: Generating the body of a function (or
initializer of a global) may need to access a constant. Though in
deferred loading mode we may not have decided (yet) into which module
to place the constant.
* We emit an unresolved constant access (kind of dummy instructions
which still maintain stack machine) as a patchable region in the
instruction stream and remember that the constant (and any constant it
refers to, directly or indirectly) was used by the corresponding module.
* After code generation we have collected all constant uses (and the
modules they are used in) and have therefore all knowledge to decide
where to place constants. (See below on placement logic)
* In a final link phase that will walk over any instructions with
unresolved constant accesses (patchable regions) and patch them up
with the actual instructions to access the constants.
Part 1) Determination of global order
So far the creation order of globals determined the order in the global
section. But now we emit unresolved global uses and later on have to
define (or import) the globals in modules.
=> To allow this we now determine the order of globals when we build the
globals section instead.
=> This would also allow other things: Choose ordering of globals based
on usage count, etc.
Part 2) Separation of concerns in `constants.ensureConstant()`
So far the `constants.ensureConstant()` has done several things:
* performed constant lowering
* analyzing whether the constant should be lazy or eager
* determine the type of the global of the constant
* actual creation of global & initializer function (if needed)
* doing the above for all transitive constants
=> The result was the `ConstantInfo` object.
We now separate these things:
The first part will lower constants, determine lazy or not, determine
type. This will recursively walk the constant DAGs and create
`Constantinfo` as needed for all of them.
=> Each `ConstantInfo` (representing information about a `Constant`)
will now also remember the child constants (in the form of
`List<ConstantInfo> children`) it will use when defining the constant.
=> When code generation uses a constant we will remember that that
module-use of the constant and all it's child constants.
=> Representing this as `constantInfo.children` avoids recursive AST
visiting, avoids re-lowering the constants and ensures we don't have
to keep two AST visitors in sync.
Part 3) Tracking constant uses
When the code generation uses a constant, we remember it being used in
the module being currently compiled. We use this usage information in
the final stage to determine where to place constants.
Special situation: If we have constant uses across modules where
deferred loading is involved. For example here:
```
import 'foo.d.dart' deferred as foo;
main() {
...
print(foo.topLevelConstant);
}
```
which gets lowered to something like this
```
StaticInvocation(target=print, args=[
let
_ = StaticInvocation(target=checkLibraryIsLoaded, args=[StringLiteral('foo')])
in
ConstantExpression(topLevelConstant)
)
```
Even though the main module is using the `topLevelConstant` it does so
under what I call a deferred loading "load guard": The code accessing
the constant will never be executed unless the `foo` deferred library
was successfully loaded.
=> We make our usage tracking consider such uses not a usage of the main
module but rather the module containing deferred library of the
"load guard".
=> The `CodeGenerator` will track the active "load guard" when it goes
down the tree.
=> This allows pushing constants to deferred modules even if they are
used in main module code.
Part 4) Defining of constants
During code generation we (generally speaking) emit a patchable region &
record the constant use of the constant DAG (see above).
During the linking phase we then have global knowldge of constant uses
and start defining them.
Theoretically we want to define a constant in a wasm module in the
loading graph where all using modules have it as direct or indirect
dependency but the dependency being the closest one to the uses.
=> As simplification for now: If two different modules use a constant we
place it in the main module. We can later on make this more precise if
complexity is warrented.
To avoid emitting many patchable regions that we later on have to fix
up we add an optimization during code generation:
=> As soon as a use is in the main module, we define the constant DAG
in the main module.
=> As soon as there's 2 uses in different modules, we define the
constant DAG in the main module.
Misc
* We separate constant definition from importing / exporting them.
* The new architecture changes constant visiting slightly so the names
of constants in expectation files change as a side-effect of this.
Issue https://github.com/dart-lang/sdk/issues/61727
Change-Id: Ib44dee4c2514fb4af871e7078f5bfe43077922fd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/458240
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
https://dart.googlesource.com/sdk/+/6184c447abe2b876de0d883fd675651c77d43821
Monorepo is:
With depot_tools installed and on your path, create a directory for your monorepo checkout and run these commands to create a gclient solution in that directory:
mkdir monorepo cd monorepo gclient config --unmanaged https://dart.googlesource.com/monorepo gclient sync -D
This gives you a checkout in the monorepo directory that contains:
monorepo/ DEPS - the DEPS used for this gclient checkout commits.json - the pinned commits for Dart, flutter/engine, and flutter/flutter tools/ - scripts used to create monorepo DEPS engine/src/ - the flutter/buildroot repo flutter/ - the flutter/engine repo out/ - the build directory, where Flutter engine builds are created third_party/ - Flutter dependencies checked out by DEPS dart/ - the Dart SDK checkout. third_party - Dart dependencies, also used by Flutter flutter/ - the flutter/flutter repo
Flutter's instructions for building the engine are at Compiling the engine
They can be followed closely, with a few changes:
goma_ctl ensure_start is sufficient.Example build commands that work on linux:
MONOREPO_PATH=$PWD if [[ ! $PATH =~ (^|:)$MONOREPO_PATH/flutter/bin(:|$) ]]; then PATH=$MONOREPO_PATH/flutter/bin:$PATH fi export GOMA_DIR=$(dirname $(command -v gclient))/.cipd_bin goma_ctl ensure_start pushd engine/src flutter/tools/gn --goma --no-prebuilt-dart-sdk --unoptimized --full-dart-sdk autoninja -C out/host_debug_unopt popd
The Flutter commands used to build and run apps will use the locally built Flutter engine and Dart SDK, instead of the one downloaded by the Flutter tool, if the --local-engine option is provided.
For example, to build and run the Flutter spinning square sample on the web platform,
MONOREPO_PATH=$PWD cd flutter/examples/layers flutter --local-engine=host_debug_unopt \ -d chrome run widgets/spinning_square.dart cd $MONOREPO_PATH
To build for desktop, specify the desktop platform device in flutter run as -d macos or -d linux or -d windows. You may also need to run the command
flutter create --platforms=windows,macos,linux
on existing apps, such as sample apps. New apps created with flutter create already include these support files. Details of desktop support are at Desktop Support for Flutter
Tests in the Flutter source tree can be run with the flutter test command, run in the directory of a package containing tests. For example:
MONOREPO_PATH=$PWD cd flutter/packages/flutter flutter test --local-engine=host_debug_unopt cd $MONOREPO_PATH
Please file an issue or email the dart-engprod team with any problems with or questions about using monorepo.
We will update this documentation to address them.
flutter commands may download the engine and Dart SDK files for the configured channel, even though they will be using the local engine and its SDK.gclient sync needs to be run in an administrator session, because some installed dependencies create symlinks.