Right now a JS interop callback works like this:
* Each wasm module that gets instantiated will be given it's module
instance (JS calls Dart to set it) via `setThisModule`
* When Dart code calls JS and gives it a callback to invoke, it gave it
this module instance. It will also make the callback wasm function
weakly exported.
* The JS trampoline code, when invoked, would then call the weakly
exported wasm function from the module instance.
We simplify this now by making the Dart code simply give the wasm
function reference to JS, then JS can later on invoke it. No need to
weakly export a function and call back via
`module.exports.<weaklyExportedCallback>`
To ensure binaryen is aware that the wasm function may be called from
JS, we annotate it via the `(@binaryen.js.called)` annotation.
Change-Id: I828dd0cf8d3b36db338792c4e277a4bb94c76faf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/511080
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Binaryen introduced `binaryen.inline` which allows us to tell it
inlining hints now, including "never inline" hint (see [0])
This allows us to remove the ugly mangling of wasm function names with
`<noInline>` postfix.
We also now pass `--strip-toolchain-annotations`: The annotations
occupy size in the wasm binary and wasm runtimes ignore them (they are
for `wams-opt` only).
Except for IR tests: Here we want to see the annotations, so we keep
them there.
We also rename the package:wasm_builder classes to clearly indicate
those are binaryen specific sections.
We also make the ir_test.dart put it's options first, allowing the IR
tests to override options if needed.
[0] https://github.com/WebAssembly/binaryen/commit/3c25487214600a9
Change-Id: I96688bfaeba403a39cd5e7376f8d2889bcbae030
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/510000
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
This reduces e main module by around 1.5% compressed and 0.7%
uncompressed.
Except for a few special wasm struct types, we don't rely on type
uniqueness as we never use e.g. `ref.test` on normal wasm structs of
dart classes, closures, closure contexts, ...
So for otherwise structurally equivalent types, we can allow merging the
types. This can lead to confusion when reading wasm files because two
unrelated Dart classes may end up being represented with the same wasm
struct. To avoid this confusion when reading .wat files, we make it
clear in the type name and field names that it can be any of the
original dart classes / fields.
One can enforce unique types by passing `--no-unique-types`.
In IR tests we default to `--unique-types` to make the IR tests more
readable, except for 2 newly added tests that test this flag.
In essence this is the follow-up work on [0] which started sharing wasm
structs between classes in a hierarchy.
We enable `--unique-types` in opt>=2 (just like in --minify)
The dart2wasm compiler may now emit two identical recursion groups. We
could avoid that, but that would be a bigger refactoring of the type
building code in `package:wasm_builder` and `wasm-opt` will remove the
duplicate recursion groups anyway.
[0] https://dart-review.googlesource.com/c/sdk/+/472181
Change-Id: I36a5b7cfc3abb5318658fa67dae36e1c0a1e4d19
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501140
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This reduces e main module by around -0.3%.
We make the inliner have more precision when estimating the
callee size, especially around constructors but also other
cases.
We are also less generous with budget for iterators, modulo list
iterators which we want to be always inlined.
We also print the inlining decisions in `--print-wasm`
In deferred loading scenario this now inlines constructor bodies
more often than before as the size estimate is better. That in
return avoids us e.g. exporting empty constructor body functions
in the main module to be imported by other modules.
CoreLibraryReviewExempt: Backend-specific function changes.
Change-Id: I320db6f976a5d8c036b40908a5f72e7019d420ae
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498562
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
The change in [0] increased the language version of pkg/dart2wasm. That
in return changes how the package is formatted by the autoformatter.
This CL runs now the formatter to re-format the code. Unfortunately this
makes blame lists worse. But not doing it will make us have to disable
auto-formatting before saving files which is very annoying.
[0] https://dart-review.googlesource.com/c/sdk/+/487944
Change-Id: Ie3cbdc58bbd7f5aba187470554b7c958719fa795
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/496940
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This reduces e mail module by -4% in compressed form. It does have
a increase of 2.5% in uncompressed form. This is due to repeated
entries in the element section of the dynamic dispatch table and
will be addressed in future CLs.
Currently if the main module of an app has a dynamic call, it will have
a caller-shape specific dynamic dispatcher that checks (via class ids)
all possible targets in the app and issues calls to them.
That means the code size is O(targets) - even if most targets reside in
deferred modules.
We change this now to use a row displacement table based dispatch,
just as our normal typed dispatch table. Though there's a few
differences
* In a typed call we know the target exists, in a dynamic call we don't
know whether the target exists (it may be a NSM case). To accomodate
for this we make 2 wasm tables of the same layout: First we load a
table of i31refs and check if the value is in agreement with the
receiver class id. If so we have a match and can load the actual
destination from the funcref table.
* In typed calls we use one row in the table for each selector. In the
dynamic invocation case we'll use one row per dynamic selector + call
shape.
* The wasm module that contains the actual instance method will also
hold all it's dynamic forwarder functions (one per dynamic caller
shape). The elements section of that module will cause initialization
of the two slots in the two tables mentioned above.
Issue https://github.com/dart-lang/sdk/issues/62639
Change-Id: I4e631f98fe9c58e2110bac34c3f5ff6d11bef909
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/487660
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
If a function doesn't have an effect we can now mark it via
`@pragma('wasm:pure-function')`. We'll then emit this as metadata
in the `binaryen.remove.if.unused` custom section.
This allows `wasm-opt` to remove calls to such functions if the result
of the call isn't used.
For now we mark a few string functions as pure.
Closes https://github.com/dart-lang/sdk/issues/62665
Change-Id: I8d38fb5894fd98248dc4d648d99c8cdcddc271a5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/481802
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
Add more Wasm SIMD intrinsics and instructions to support efficient
vector operations, specifically targeting the requirements of types
like Offset and Size.
- Implement new intrinsics in `dart2wasm`:
- `anyTrue` for V128.
- `allTrue` for I64x2.
- `pmin`/`pmax` (pseudo-minimum/maximum) for F64x2.
- `fromLaneValues` for F32x4 and F64x2.
- `shuffle` for F64x2.
- Update `wasm_builder` with support for the following instructions:
- `v128.any_true`
- `i8x16.all_true`, `i16x8.all_true`, `i32x4.all_true`, `i64x2.all_true`
- `i8x16.shuffle` (including serialization and deserialization)
- Add `tests/web/wasm/simd/vector_test.dart`, which implements `Offset`
and `Size` using these new SIMD intrinsics, demonstrating their
practical utility and correctness.
- Update `tests/web/wasm/simd/simd_test.dart` with additional coverage.
Change-Id: Ifa5ba0fb265b0fa46c0a3469d9331f32a025c9ec
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/478860
Commit-Queue: Kevin Moore <kevmoo@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This seems to result in -0.6% compressed main module and a bit less
in uncompressed mode.
Sometimes we have many fields with lazy initializers of the same type.
That led us to emit 1 nullable wasm global for each such field. For
example all proto classes have a `static BuilderInfo i_` field. This has
led to thousands of `(mut (ref null $BuilderInfo))` wasm globals.
Now we use a wasm table for this, which is a O(1) in the binary as they
all get `null` by default, saving us all these globals. The downside is
that accesses have an extra instruction now, but overall this is a win.
The CL also cleans up `globals.dart` by separating the concept of a wasm
global and reading/writing to it from the concept of a Dart global - as
Dart globals can now be backed by wasm globals or table slots.
This CL uses the new capability made possible by the refactoring
in [0] - namely to emit element sections which initialize wasm table
slots with non-function expressions.
[0] https://dart-review.googlesource.com/c/sdk/+/459440
Change-Id: Ie57206dff8c0a57a1df5e48c0808167f822bc4a2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/475800
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reuse dart2js's source map extension format to map minified class names
shown in error messages and runtime type strings ("minified:Class123")
to original class names.
The extension field is only generated when minifying, and with multiple
modules, only in the main module's source map.
- Without minification runtime type strings and error messages already
include the full class name, so mapping is not necessary.
- With multiple modules, the existing tools use the main module's source
map to deobfuscate errors.
Because wasm-opt is not aware of this custom section and it also changes
the names section, when optimizing, we read the original names before
calling wasm-opt, and write the section back to the source map file for
the optimized Wasm.
When not optimizing we generate the source map with the custom section
directly.
Example deobfuscation using the new source maps:
$ dart pkg/dart2js_tools/bin/lookup_name.dart test.wasm.map Class123
Class123 => AsyncError (a global name)
The custom section is a bit verbose for what dart2wasm needs: we could
map numbers to name indices instead of strings to name indices, as
dart2wasm minifies class names to numbers. However to avoid updating a
bunch of tools in g3, SDK, maybe also in Flutter and devtools, we reuse
dart2js's format, at least for now.
Issue: https://github.com/dart-lang/sdk/issues/60711
Change-Id: I2cda331723c6f0c7e7ef5f4772feaf420dc8c6ba
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/474660
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This reduces essentials main module by 3.5% and the type section by
37%.
So far if there was a call from any module Mi to a function F in module
Mj the main module had to add a table whose type is the function type of
F. Encoding this function type also means encoding any type the
function type transitively refers to.
=> That means that if classes are contained in deferred modules we still
pay the price of encoding their types in the main module -
which we really don't want.
Instead of doing this, we now make a single table containing values of
type `HeapType.func`. The module containing the definition of the
function will initialize the table with the slot. Any caller can use
`call_indirect` to call the function.
One may argue that the table having a less precise type means more
overhead at runtime. Though for several reasons I think this is the
right call to make:
* the performance of cross module direct call will now be similar to a
interface call via dispatch table
* the call site will have the identical function type as the callee and
as such the type check should be cheap / a pointer comparison
* there's no polymorphism at runtime, a call always goes to the same
destination and as such it's a very predictable branch for the CPU
* it has to be an indirect call anyway and some engines have similar
support for both `call_ref` and `call_indirect` (e.g. speculative
inlining)
Change-Id: Ibc7680afd9acb4f969019d27e9f13c24aa6a5706
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/475200
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
For a variety of reasons the dart2wasm comiler can currently emit empty
modules (e.g. function is assigned to a module but actually the function
is dead but wasn't tree shaken by TFA, all functions in a module got
inlined to callers, etc)
=> Only emit modules that are not empty.
=> Prune `loadids.json` of those empty modules.
This results in compilation of essentials containing 263 less modules.
Change-Id: I31e9277450d5738da08f09220dbbabc270c1a53b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/475100
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This adds the `Memory` class to `dart:_wasm`, allowing Dart code to
load and store numeric types in linear memory.
Since `dart2wasm` doesn't generate a memory instance by default, there
is no singleton instance of `Memory`. Instead, memories are defined as
`external` top-level getters annotated with a pragma like
`@pragma('wasm:memory-tyype', MemoryType(limits: Limits(1, 10)))` to
declare their type.
Interop happens in a static way: Methods on `Memory` cannot be torn-off
and, since the target memory is encoded directly in the store/load
instruction, there's also no polymorphism for memories in Dart.
Attempting to call methods on a memory instance that isn't a direct
reference to its definition is a compile-time error.
Memories can also be imported and exported through the existing
`wasm:import` and `wasm:export` pragmas.
TEST=tests/web/wasm/memory_test.dart
Change-Id: I726f33ac2ec04afab55c5a2b6bc09079d0193e02
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/470020
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Prior to recent changes that moved wasm-opt into a phase of dart2wasm, the optimized module always had the same filename as the unoptimized one. So even though wasm-opt was not updating the source map URL in the module, the URL was still correct.
However, now we can specify a new name when we run wasm-opt on its own. So we need wasm-opt to update the URL in the module.
We always pass this flag but when the names match wasm-opt will just update the URL to the same value.
Change-Id: I1e59a6189cd537b1e614431ba1ef7bc5e70cb088
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/471380
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
- When generating `end` instructions starting a state machine block,
generate a comment showing
- State index
- Type of AST node that the state is for
- Whether the block is for the "inner" or "outer" (i.e. continuation)
of the AST node
- When a local has a name, show it in `local.{get,set,tee}` traces.
Also removes an old TODO comment.
Change-Id: I80848f2fbb4d25458e5cdb27ed1f7def4ca9899e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/470160
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
Example:
import 'dart:js_interop';
@JS()
external void eval(String code);
@JS()
external void throwFunction();
void main() {
eval('''
self.throwFunction = function() {
throw new Error('Hi from JS');
}
''');
try {
throwFunction();
} catch (e, st) {
print(e);
print(st);
}
}
Output before: ("..." parts are code locations, omitted)
JavaScriptError
at module0.main ...
at module0._invokeMain ...
at InstantiatedApp.invokeMain ...
at main ...
at async action ...
at async eventLoop ...
Output after:
Error: Hi from JS
at self.throwFunction ...
at _277 ...
at module0.main ...
at module0._invokeMain ...
at InstantiatedApp.invokeMain ...
at main ...
at async action ...
at async eventLoop ...
Fixes#62218.
Issue: https://github.com/dart-lang/sdk/issues/62218
Change-Id: Ia9347e938af209b8b87752479d35b6236f721acf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/469062
Reviewed-by: Martin Kustermann <kustermann@google.com>
This reduces essentials main module by 8.3% (-730 KB)
The dispatch table contains displaced selector rows. Each selector row
contains an entry for each class that provides the selector.
This can lead to very large dispatch tables with repeated elements:
Especially common is a base class with selectors that get inherited
by many subclasses where few subclasses override the selector.
This is common e.g. for `Object.{hashCode,operator==,noSuchMethod}` but
also for user defined base classes that have many subclasses.
This led to the element section being very large: It often contains
large consecutive sub-ranges which refer to the same target function.
To shrink the element size we instead move the initialization of those
large regions homogenious regions to the module init function: We can
utilize the `table.fill` instruction which can initialize a large range
of the table with the same value.
Change-Id: I9fc308969264f4855a514e7d36c761891bb0cb52
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467843
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
This shrinks essentials main module by 13% (-1.4 MB)
Before this CL the splitting of the application into wasm modules
was done on a library granularity level.
Now we split the application based on "static element" granularity -
those elements are:
* Static fields
* Static getters, setters, methods
* Constructors
* Class (all instance fields & methods)
This means that moving a class or static methods/fields from one library
to another will have no effect on the partitioning.
Differences to dart2js:
* No tracking of local functions
* No tracking of types
* No split constraint support (yet)
* (Works on Kernel AST instead of dart2js element/entity model)
The code is organized into
* `pkg/dart2wasm/lib/deferred_load/import_set.dart`
This is almost identical to the dartj2s version with minor differences:
- works on `LibraryDependency` objects
- does not assign names to parts
- no split constraint support (yet)
* `pkg/dart2wasm/lib/deferred_load/dependencies.dart`
This is a new implementation that collects dependencies of
`Reference`s/`Constant`s and in case of `Reference` whether the
dependencies are deferred or not.
* `pkg/dart2wasm/lib/deferred_load/partition.dart`
This is the main algorithm (core logic is the same as in dart2js)
`Reference`s/`Constant`s and (in case of `Reference`) whether the
dependencies are accessed under deferred loading guard or not.
Change-Id: I0fcdf86f5226060738671a1f69bb14cfffd631e8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467041
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
To avoid depending on exact ordering binaryen uses in the wasm module,
we start
* enqueue functions/types/... based on sort name instead of index
in wasm module
* only assign increasing ids in `Namer` if we actually want to print
the name
* print functions/types/... in sorted order
for IR tests.
We also update the `pkg/dart2wasm/bin/wasm2wat.dart` tool to be
able to dump wat for multiple wasm modules.
Change-Id: Ifbfc9149a629cf5ceacd95b7cb4892e9de3d4d9c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/465341
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Until now the element segments were hidden from the core IR & Builder
classes. This has a few issues as outlined here:
* It required `ir.DefinedTable` and `ir.ImportedTable` to have entries
=> Having element segments as core concept means they can refer
to a `ir.Table` (irrespective of wether it's a `ir.DefinedTable` or
`ir.ImportedTable`)
* It assumed that table entries have to be functions
=> We can have tables of other reference types as well
* If we ever wanted to use the `table.init` instruction then we'd be
having an issue as that instruction refers to an element segment
(which didn't exist in our IR)
=> Now we represent `ir.ElementSegment` as a concept which a
`ir.TableInit` instruction could reference.
* The encoding of function tables was so far either using the
`vec[ref.func]` form if the table index was 0 or it
would use `vec[expr]`.
=> The active segment encoding with/without table index is somewhat
orthogonal to whether to use ref.func/expression.
=> If we encode a function table with index != 0 now we can still
use `vec[ref.func]` (which is smaller encoding)
=> If we encode non-functions in a table we can now use `vec[expr]`
encoding.
* Makes IR, serializer & deserializer cleaner.
=> removes `ir.Functions.withoutDeclared`
=> removes deserializing functions with side-effects
Change-Id: I75544ab3b1f0c721f0585a2c187a81415e978fe3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/459440
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
* Assign type indices when deserializing the type section
* Fix decoding of tag imports
* Add data segment support to IR printer
* Handle input+output variants of loop/if/try_table instructions
This allows us to now read & print all wasm modules of ACX gallery when
compiled in deferred loading mode.
Change-Id: Iaba30a6337aa2afd5d4894ca73d305cd2b7befea
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/459480
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
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>
This CL does a few things
* only have one way to lower deferred loading constructs
* only have one runtime implementation
* only use load ids now and refer to them as integers instead of strings
* remove AST repository for load ids (no need to serialize them across
kernel serialization - as we assign them during codegen phase now)
* make runtime metadata smaller for deferred loading (wasm arrays)
* inject the loading map (runtime data structure) after codegen
* it fixes the stress test module strategy to inject `LoadLibrary`
AST nodes (as the CFE does) instead of calls to the lowered form
Overall this simplifies the code significantly, removes complexity &
code from the codebase.
This refactoring opens up for the possibility for the codegen phase
to add more modules (e.g. if two deferred modules use the same constant,
we could *create* module to contain them).
Closes https://github.com/dart-lang/sdk/issues/61844
Issue https://github.com/dart-lang/sdk/issues/61727
Change-Id: Idfefee25d5f84f8717808a0aee1b189d8f15d16d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/458000
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Changes to IR printing implementation:
* Emit omitted `<...>` marker to make difference between function
without body and function with omitted body clear.
* Extend omtting capability to globals, types and tables
* Extend filtering capability to globals, types, tables
* Better table names: Try to use import or export names if available.
* Option to print globals & types always multiline
* Option to scrub absolute file uris (which aren't stable across machines)
Changes to IR binary / text:
* Extend binary parsing of element section to support imported tables
* Add printing of those (which shows how imported tables are patched)
(The patching of importing tables is used in deferred modules)
Changes to IR test framework:
* Allow more filters (see above) in the test files.
* Allow tests to use helper libraries (containing `.h.` in their name)
without them being considered tests themselves.
* Allow using deferred loading in tests and write expectation files for
main & deferred modules as wat files.
Other things:
* Share more code between `pkg/dart2wasm/bin/wasm2dart.dart` and
`pkg/dart2wasm/test/ir_test.dart`
* Add baseline test for deferred loading using tear off constants
Change-Id: Ica6666f23f5aa6fb2174f414c9082039940f8dc5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/456460
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
In order to test generated code for a function one can
* place a dart file in `pkg/dart2wasm/tets/ir_tests`
* annotate functions that shouldn't be inlined
* describe which functions we want to dump in the expectation file
* generate an expectation file.
This will allow generating renatively small expectation files for only
functions we care about and types/globals/... those functions need.
Change-Id: Ic7b6b6dece16ab453202aa2c4f9412de2fc251ae
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/454840
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This adds support for printing module IR as text format.
For convenience we add a `pkg/dart2wasm/bin/wami.dart` that
produces very similar output to V8's `wami`.
The goal is to use this to write size/perf optimization tests
by dumping IR into expectation files (will add this
infrastructure in a future CL)
Issue https://github.com/dart-lang/sdk/issues/60928
Change-Id: I42d19c2b8c6242f55693d6ed5d844a5c1ecb1f39
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/454600
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
This adds a wasm binary reader that produces an `ir.Module`.
We also make a few changes to existing code
* Represent the import section with an `ir.Imports` object (similar to
`ir.Exports`, `ir.Functions`, ...)
* We make a bunch of data structures allocatable in uninitialized state
(the fields being usually uninitialized `late final` fields) where the
deserializer can create those objects and then fill in details later.
=> This comes partly due to the way wasm binaries are structured
themselves: The "data count" section comes first so a reader knows
how many data sections there will be, then the "code section" can
refer to those data sections. Then afterwards the actual "data
segment" comes that fills in the data of the section.
* We make names consistently optional: Wasm objects don't have to have
names, so the names should be optional, so we make them `String?`. We
also make them non-final as that's consistent with other names.
* We make the `ir.Types`, `ir.Functions`, ... objects have `operator[]`
and the index used is the same index used e.g. in wasm instructions.
* We make static constants for section ids and custom section names.
Issue https://github.com/dart-lang/sdk/issues/60928
Change-Id: I5394d6b82cf4dc68d24cea1dee66c5b33eb2f60f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/452144
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
Most call, struct and array wasm instructions refer to the type using an
unsigned index. Currently the serializer uses a signed index, which
emits extra bytes.
Change-Id: Id8a55497c45692956bc8ea36268d9b38c8025933
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/452442
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Currently the IR has some serialization information attached to it (e.g.
it keeps track of the number of types that have names, etc).
This is redundant/derived information as it can be computed by looking
at the actual IR (e.g. iterate types and count the number of them that
have names).
=> We get rid of this impurity by making the serializer compute the
information it needs to serialize.
I suspect it has been done as an optimization, to avoid an extra pass
over the IR data structures. But this can be handled in another way
witout extra passes as well:
* a single pass traverses and writes the (index, name) pairs
* it keeps track of the number of (index, name) pairs written
* if the number is > 0 then we add the section, the count and the pairs
We do have to traverse all the functions, types, ..., at least once now,
but that's fine: Firstly we always assign names so in reality we cannot
skip these traversals (we instruct binaryen to emit or skip the names
when it runs). Secondly if we wanted to have core dart2wasm not emit
name section we can just skip serializing it if e.g. a
`--no-name-section` flag was passed.
We do a few more changes in this CL:
* We make `ir.DefType.name` be an optional string
=> This aligns it with `ir.Global.globalName`, `ir.BaseFunction.name`,
...
* Only assign `localNames[local] = ...` and `fieldNames[field] ` ...`
if name is not empty string
* We put the `sourceMapUrl` section behind the name section (to align
with the order used in binaryen)
* We remove redundancy in the section serializer: Instead of having
`isEmpty` that has to be kept in sync with `serializeContents` we
only have `serializeContents` and make the caller check if the bytes
are non empty.
Issue https://github.com/dart-lang/sdk/issues/60928
Change-Id: Ie9d153fefc25e8277fb30b83e45d2549731797c4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/452040
Reviewed-by: Ömer Ağacan <omersa@google.com>
We have a relatively clean separation between the data structures
representing a wasm module (e.g. `Module`) and the classes used to
build such a module (e.g. `ModuleBuilder`)
Though with deferred loading changes the core IR data structures started
to get a `ModuleBuilder get enclosingModule` getter.
This CL makes the IR classes self-contained again.
=> The `enclosingModule` getter will now return a `Module` instead of a
`ModuleBuilder`
=> The `*Builder` classes that need a `ModuleBuilder` can store that
themselves but only pass up `Module` to IR classes.
Since the IR data structures are cyclic (the `Module` has various parts
that refer back to it via `enclosingModule`), we allow the `Module`
object to be constructed in an uninitialized state and initialize it
after building the module is done.
Issue https://github.com/dart-lang/sdk/issues/60928
Change-Id: I5fddd6ca43b662a6e6c328230e91b1bc75332deb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/451720
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Local names for function value parameters and for "precise this", return
values are generated, state indices in `async` and `sync*` functions are
generated.
We can generate names for more locals as needed.
Change-Id: Ie919f030f0bfae8adbca90408509dd04a7414278
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419200
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
These instructions are not used yet as they're not enabled by default
in Chrome yet.
This CL is mainly tested by the child CL, which uses instructions added
in this CL for exception handling.
Change-Id: I04d767599f47cdb6abc9cca02974647d9e5421fb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/411581
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Introduces some more machinery to simplify the serialization pipeline. Now the logic to index kernel nodes is hidden away in the serializer. Now throughout the compiler if entities (i.e. classes, members and references) need to be serialized they can be passed to the serializer directly.
Also adds support for tracking brand type assignments from the main module to the dynamic module. This is necessary to decouple the execution order of both compilations which is a WIP.
Change-Id: I38dd0ed11811ea12a2230959588b261719976592
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/412461
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
Missing from this implementation:
- Closure/dynamic calls with differing signatures
- Overrides with extra optional parameters
- Records with same shape defined in different dynamic modules
- Avoiding running TFA on dynamic module.
- Recompilation of only updateable functions from main module.
- Persist wasm def types from main module.
Testing is currently done locally via the dynamic_modules package test suite:
dart pkg/dynamic_modules/test/runner/main.dart --runtime=dart2wasm
Immediately after this lands we can introduce a new step to one of the wasm test matrix configurations that runs the above test suite (the VM has a similar configuration).
Change-Id: I3386d84be11b773842d45f4268a62a54c47e352b
Tested: Tested via new tests in dynamic_modules package. Tests run locally but will add to existing config.
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/397721
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>