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>
Now that we have a way to tell binaryen to inline functions (see [0],
which added support for `(@binaryen.inline <0...127>)` annotations) we
can tell it to inline functions that were annotated with
`@pragma('wasm:prefer-inline')`
Since binaryen sometimes can devirtualize call sites that TFA cannot, it
can then inline those devirtualized calls.
[0] https://github.com/WebAssembly/binaryen/issues/7972
Change-Id: I139bd43976a1ddb83afe756d4fbac09419f7199e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/510822
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@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>
Static field initializers are only run once and as such are cold code.
They should never be inlined into hot code that accesses the static
field.
Though right now `wasm-opt` will inline the static field initializers
e.g. if there's only one use of a static field (as it makes size a bit
smaller if we avoid the extra wasm function). That has the problem that
it may inline cold code into hot code. That hot code in return then gets
big and may not be allowed to be inlined into it's caller.
One can instruct binaryen to only inline functions with one caller up to
some size by passing `--one-caller-inline-max-function-size` (see [0]).
Though that can regress size.
So instead we specifically mark the function we know are cold to not be
inlined, namely static field initializer functions above a certain size.
This changes performance of a benchmark from [1] from
cascadeUnhoisted: 390000 us
to
cascadeUnhoisted: 138000 us
NOTE: This will cause some size regressions for e.g. e main module by
0.4-0.5% - but seems to be the right thing to do.
[0] https://github.com/WebAssembly/binaryen/issues/8619
[1] https://github.com/dart-lang/sdk/issues/63484
Change-Id: I626b8cd879a804aef638901ede190f74276d8e8b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/509960
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Similar to [0] which recognizes functions that return always nulls, we
recognize functions that never return and
* make the wasm function have no outputs
* make call sites emit `unreachable()` after the call (to inform
binaryen & wasm runtime that this is unreachable)
Before we had an artificial construct where we made such functions
have a `w.RefType(HeapType.none, nullable: false)` return type (i.e.
bottom, i.e. no values) and encoded that way it's unreachable.
We also change some exported wasm functions to explicitly use wasm
types in the signature (namely `WasmVoid` instead of Dart `void`).
[0] https://dart-review.googlesource.com/c/sdk/+/497620
Change-Id: I3724e777cda23c0cf2c8a7dd2e473f3fef0a4f54
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499240
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
When there's no expression on the stack but we expect something on the
stack, then the code should be unreachable.
Though the current code would just synthesize a value that matches
the expected type (`convertType(voidMarker, <some type>)`). This
is problematic: If we ever used that synthesized value we may
have incorrect program behavior.
Now there were some valid uses where we synthesize values
* A function that has `void` return type but no explicit return
=> Here we should synthesize `null`
* Synthesize `null` in cases where we know it's not going to be used
=> e.g. for CFE desugaring of `a[i] = b` is roughly
`let tmp = b in (let ignored = a.[]=(tmp) in b)`
where we synthesize `null` as `a.[]=(tmp)` result,
`ignored` isn't used.
* ...
With this CL we no longer allow synthesizing a value of a type
out of thin air, instead all the places where this occurs have
to do that explicitly.
There's some impurities around how setters and index setters
are handled today (and even after this CL). Those impurities
start all the way at CFE, which treats setters and index
setters very differently. See the CFE issue [0].
For those we have two choices:
* special case all call sites that require synthesizing
null values
* special case all call sites that require dropping an
auto synthesized null value
This CL now marks instance setter/index-setter methods as
requiring auto-synthesizeing null values on usage sites and
make code that doesn't need them explicitly drop them.
Somewhat related to this change is how we deal with `void`
on the Dart <-> Wasm Import / Wasm Export boundary: When we
call an imported wasm function that has `void` as return
type (meaning no return values) we have to synthesize a `null`
(as the caller may "use"/"observe" the `void`).
=> We now are more strict and instead use `WasmVoid` as type
instead of allowing `void` as type on the import/export
functions.
[0] https://github.com/dart-lang/sdk/issues/63360
Change-Id: Ie30df3bd68553724437607bab3163c98f5467efe
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501061
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Move the creation of
* name of a lambda to `translator.functions.getLambdaFunctionName`
* type of a lambda to `translator.functions.getLambdaFunctionType`
Removes dependency on `w.FunctionBuilder` in the lambda code generators
- as they don't need access to the wasm function. This may allow
inlining closure calls in the future.
Avoid creating `w.FunctionBuilder` eagerly when analyzing closures and
instead create it only when there's a call to it (or the closure object
gets instantiated).
Use `CallTarget` abstraction when invoking lambdas.
Avoid carrying around
`(lambda, enclosingMember, enclosingMemberClosures)` throughout
the codebase and instead store this information on `Lambda`.
=> All these changes make the codebase more uniform between
lambdas and normal functions and also is net code removal.
Change-Id: Ib69566ea9580827be0ed52c0c179884e84599d88
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501983
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
This gives around 0.2% improvement in compressed e main module.
In Dart a function with `void` return type can actually return values
that callers can observe. But most of the time this doesn't happen, most
times those functions return `null` values and callers don't observe
them.
Let's use inferred return value information to see if a function is
guaranteed to only return `null`. If so we make the wasm function
signature not return any values. Callers will then synthesize a `null`
which may immediatly be dropped or (in rare cases) actually be used.
This leads to less less instructions in the callee (as a callee doesn't
need to push the null onto the stack) and the caller (as the caller
doesn't have to drop it from the stack).
Change-Id: I3ed1be7592798ad0c697c5bc3ab2c4b64c156f03
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/497620
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
This reduces the size of both the mjs file (which contains the JS implementations) and the wasm file
(which includes these names as import/export names).
The savings here is relatively small for most apps.
A main impetus for this is the useful side-effect that it makes the names more stable. By assigning
names after TFA has run, the indices don't change when an unused JS method is added/removed. This
helps make the ir_tests more stable.
Change-Id: If47e3460d060a979bd41ef430c13c6828d98b549
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/494720
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This reduces essentials main module around -0.4% and possibly
opens up for changes in the inlining (specifically to possibly
not force-inline all initializers anymore)
This shrinks the amount of information
* initializer result values
* the body parameters
* the allocator needs to forward less from initializer to body
We do that by analyzing constructor parameters to see
which parameters are needed for the constructor
Change-Id: I967fa4102ea6e9d498ff07aedabc368b038e1085
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/496341
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: I6953fe0d6a824b2b79a26bbadb0bb977cec70b7a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/490821
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>
The CL does a few things:
* Rename "forwarder" to "dispatcher" to disambiguate them from actual
dynamic forwarders:
- "dynamic dispatcher": A function that takes all arguments of a
dynamic call & dispatches them to the right target (which is a
dynamic forwarder).
- "dynamic forwarder": A function that takes arguments, type checks
them, unboxes them (if applicable), calls the real function, boxes
the result (if applicable).
* Make dynamic setter dispatchers & forwarders have `void` return type.
Setters in Dart don't produce values, the call sites turn `a.b = c`
into something like this: `let tmp = c in (let a.b=(tmp) in tmp)`
* Make `CallShape` remember not just the argument shape but also whether
it was a getter, setter or method.
Issue https://github.com/dart-lang/sdk/issues/62639
Change-Id: Ia616f504a3ed3e9fa86a93d274af794b9437e5ed
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/488081
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Currently a dynamic call will call a helper which will create
3 array objects (for type, positional and named arguments).
It will then pass those wasm arrays to type checker methods.
Those will then get values out of the array, type check and call
the target.
We change this now such that in the normal case (**) we avoid
the array creations. Instead we make a dynamic forwarder function
per target and call shape.
We also outline the array creation when creating `Invocation`
objects. So for simple caller shapes - such as `clone()` the
creation of an `Invocation` object is a call without arguments
instead of various array creations.
Issue https://github.com/dart-lang/sdk/issues/62640
(**) If the dynamic call could be call-via-field we still create
those arrays, as we use them for closure type checking.
Change-Id: Ia8f3f3cd95f650bd8706a15ed68a43db4de80a6e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/487020
Reviewed-by: Srujan Gaddam <srujzs@google.com>
The CL changes a few things
* do not use instance field references in partitioning (they do
not have a storage location - compared to static fields
which need to be placed into a specific module)
* allow static getters and setters to possibly reside in different
modules
* separate concept of static field initializer function vs static
field storage (storage needed for getter&setter, init function
only for getter)
* fix `reference.isSetter` to also check for `isTypeCheckerReference`
* do not create class->method dependencies on abstract instance
methods
* run partitioning tests with compiler assertions
Change-Id: I61231b912c3f73f67a4d476f35113859694a80c7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/485441
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
This avoids extra `ref.null none` in the `[]=` method bodies and
avoids `drop` instructions on each call site.
This is analogous to how we treat setters.
Change-Id: Idb66c258c64856e0adcbd1c8e902851dfa640d15
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/485221
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This removes 60 kb from essentials app main module (~0.8%)
We have code patterns such as
```
main() => print(Foo('prefix-'));
class Foo {
final String foo;
final String bar;
Foo(String prefix)
: foo = prefix + 'foo',
bar = prefix + 'bar';
}
```
If we can tree shake `foo` and `bar` fields, we still emit code for
evaluating the field initializers (as they could have side-effects).
Though for String.+ we know there's no side-effects, so we can mark the
method as `@pragma('wasm:pure-function')`. That in return will allow
binaryen to remove unused field initializer evaluation code like the
above.
Change-Id: I8a99bc35bfa252b3043bbc35405b68d03bc1184f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/484980
Reviewed-by: Nate Biggs <natebiggs@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>
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>
Make `ExportNamer` a state of `Exporter` and make it private.
`Translator` has too many fields already and it maintains too much
state. Making `ExportNamer` a private field of `Exporter` makes the
translator API simpler as it needs to initialize one less field, and
exporter API becomes simpler with one less type. `ExportNamer` is also
not aliased in multiple classes now, which makes it easier to understand
how it's used.
Change-Id: Id32c25ca518ca211de8202647fd0974968402665
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/472220
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This reduces main wasm module a bit (<1%) as it avoids the main module
from having to define and export simple accessor (e.g. field accessor)
functions.
There's two scenarios to consider:
Deferred module calls function from main module
=> No issues
Main module calls function from deferred module
=> This call must have been preceded by a CheckLibraryIsLoaded
=> The wasm modules of the deferred import must have been loaded
=> No issues.
We also change the inlining to be a little more conservative on static
field getters, as a static field may need to do a lazy initialized check
and call initializer function.
We also give the initializer function a better name.
Change-Id: I5d9b138c9fcb41f8580602fd1ba9ec5aa4af5341
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/467820
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
There was a bug in `CallTarget.signature` which triggers if there's only
one entry that's statically dispatched against, which causes us to
inline the polymorphic dispatcher, which relies on this (previously
incorrect) signature.
The CL also changes the dispatch table building logic to not allocate
table entries for the statically dispatched regions (as they would
never be used).
Change-Id: Ic2d0c387e8863e89ef811e1892642fe81df9189a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/468000
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
If a member is not considered "callable" from submodules, user code
should not be able to invoke it. However, dynamic module subclasses may
"inherit" the member. Virtual calls in the main module may then try to
invoke the inherited member.
We need to support updating the selector row in the dispatch table with
the class ID of the new subclass. This is similar to an overridable
function except that the slot will be filled with an imported member
from the main module (not a newly defined member).
See https://dart-review.googlesource.com/c/sdk/+/457441 for more
context.
Bug: https://github.com/dart-lang/sdk/issues/61813
Change-Id: I4162931747149a7356909ec62084375fc766b0cc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/458160
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: 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>
We have means of serializing References and Constants directly, so it is
unnecessary to generate IDs as indices. This does cause the export name
mapping to get split up a little (just as callable reference IDs were
stored separately from constant export IDs).
This is because the kernel metadata repositories are serialized by the
kernel's BinarySink, which correctly handles Constants, but cannot
serialize References from the TFA'd component to the metadata of the
un-TFA'd component. Meanwhile, the dart2wasm DataSerializer can
serialize these References, but does not handle Constants.
Therefore, the callable reference mapping is stored on the main module
metadata (just as callable reference IDs were), while the constant
mapping is stored in a metadata repository (just as constant IDs were).
The exporter must then be aware of both of these to be used as a
centralized interface.
Change-Id: I1c0fc3eed5a96b3c3e755826749ebd29c3fa2b4b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/446990
Reviewed-by: Nate Biggs <natebiggs@google.com>
Accomplishes this by serializing more metadata in the main module metadata:
1) Most of this new metadata is to have access to the main module's dispatch table from the dynamic modules, including ProcedureMetadataAttributes and the DispatchTable itself.
2) Indices to create correct calling names from the dynamic module into the main module (or into the global updateable functions "table").
3) Basic tree-shaking information about classes (e.g. did TFA fully delete a member or just delete its body).
Some metadata from TFA is still expected throughout the compiler. For dynamic modules, we create pessimistic versions of this information and attach it to the new Component.
All the same dynamic module tests that were passing (or failing) before are still in the same state. This significantly speeds up compilation of dynamic modules though as only necessary code is compiled and TFA is not run.
Change-Id: I109f53cf5dcbe6579c0f78e71ce7779d593455e9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/415500
Reviewed-by: Martin Kustermann <kustermann@google.com>
We explicitly & unconditionally export functions to JS via the
`@pragma('wasm:export', '<name>')` annotation.
This is mainly useful for external APIs that the JavaScript side can
invoke - for example `$invokeMain()`.
Though we currently use the same mechanism also in other places where a
Dart function `A` (if used) calls to JS which calls back into Dart via
calling exported Dart function `A*`.
The issue is that this mechanism doesn't work very well with tree
shaking: If the function `A` is not used it will be tree shaken. We will
then also not emit the JS code, but we still compile the exported
function `A*` as it's a root due to `@pragma('wasm:export', '<name')`. We then also have to compile everything reachable from `A*`.
This is the case for a few functions in the core libraries but even more
pronounced in code that the modular JS interop transformer generates for
callbacks: It generates `|_<num>` functions that call out to JS which
call back into a dart-exported `_<num>` function. The former may be
unused & tree shaken (as well as their JS code) but the ladder are
force-exported and therefore treated as entrypoints.
This CL solves problem by
* Mark function `A*` as weakly exported via
`@pragma('wasm:weak-export', '<name>')`
=> TFA will not consider such functions as entrypoints
=> TFA will only retain such functions if they are referenced by
other functions that aren't tree shaken.
=> The backend will export such functions as `<name>` if they are
referenced by any other code that's compiled.
* Making the code that calls function `A` also reference (but
not use) `A*`.
=> This will make TFA retain function `A*` if it retains `A`.
=> In core libraries we manually reference `A*` in code that uses
`A` and mark `A*` as weakly exported
=> In JS interop transformer we emit similar code for callbacks
=> We refer `A*` by using `exportWasmFunction()` which is an opaque
external function that prevents TFA and backend from optimizing
it away - so the function will be generated & exported.
Overall this CL ensures we only keep the exported functions if we
actually need them (i.e. we call to JS and JS calls those exported
functions).
This shrinks stripped dart2wasm hello world file in -O4 from
28 KB to 12 KB.
It also enables tree shaking of callback using JS interop code.
Change-Id: Ie81eac49cbcb574d569ea95a90538e8f417e2a12
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/415220
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: 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>
Now that we have support for generating checked & unchecked entrypoints,
we can make dispatch tables also target unchecked entry points.
This is beneficial especially in cases where there's dispatches on
`this` that require covariant checks but we don't know the target method (i.e. we cannot devirtualize it because the method we
dispatch to may beoverriden).
We keep the existing selectors that we have, but a selector will now
have
* one row if none of the implementations of the selector need to
perform type checks
=> `SelectorInfo` has a `SelectorTargets _normal`
* two rows if any of the implementations of the selector need t
perform
a type check
=> `SelectorInfo` has a `SelectorTargets _checked`
=> `SelectorInfo` has a `SelectorTargets _unchecked`
Once an unchecked entrypoint is also used in the dispatch table (only
if there's any unchecked calls to that selector) then binaryen can no
longer optimize the signature of the function. It means we may have
perform e.g. downcasts / boxing in the unchecked entry where we
wouldn't do before (because we only had static calls to unchecked entry
before this PR).
So we're going to force-inline calls to unchecked entrypoints. This
avoids sometimes down casts and boxing. It also seems to actually
shrink the binary size.
Change-Id: I3ba4980c42886cc883fb610533f5fac9cce39b65
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/407740
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
If an instance member is a normal method or a setter, it has arguments
that may need to be type-checked. In some situations we know that we
don't have to perform them at all (e.g. if we know there's only
dispatches on `this`). In other situations we have guarantees on
individual call sites that we can skip the checks.
Up until now we have only taken advantage of the call site guarantees
when we decided to inline the target (then we avoided doing the type
checks).
In this CL we will make this also work if the target isn't inlined, but
called: Whenever a member has any parameters that we need to type check,
then we will generate checked & unchecked entrypoints. Both of them
do the optional parameter handling, but only the checked entrypoint will
perform the type checks, the unchecked entrypoint skips them. Both
unchecked & checked will call to a body function that has the body of
the member.
=> We will skip the type checks whether we inline the target or not.
The dart2wasm compiler currently represents targets it can call via
via `Reference`s: A member may be used in different ways: as a tear-off,
as a setter, type checker, etc.
=> We introduce now 3 more `Reference` kinds, namely checked, unchecked
and body.
=> The rest of the compiler is adjusted to also handle those new
`Reference` types.
All calls in the code generator that may target members that could have
checked and unchecked entrypoints now use
```
Reference getFunctionEntry(Reference target, {required bool uncheckedEntry})
```
We maintain an invariant throughout the code base that a function
* **either** has only one "normal" entry if no arguments need type checks
* **or** has "unchecked" and "checked" entries (which both call a "body")
The dispatch table currently has only "normal" or "checked" entries in
it. So the "unchecked" entries are (if used) always called directly.
=> We only generate "unchecked" if there's direct unchecked calls.
=> We only generate "checked" if there's direct checked calls or
dispatch table calls.
=> If only one entrypoint ends up being generated, binaryen can inline
the body into the entrypoint function.
=> If both entrypoints are present, binaryen may often inline the
unchecked one into call sites that then directly call the body.
In a future CL we may allow calling unchecked entries also via the
dispatch table.
Overall this approach leads to minimal changes to code size changes, but
brings -O2 performance closer to -O4.
Change-Id: Ic3082cc397335b969fd413f72652cc5af753adf7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/406980
Reviewed-by: Ömer Ağacan <omersa@google.com>
This allows wasm optimizers as well as wasm runtimes to optimize code
better as they know calls to slow paths that throw will never return.
Change-Id: Iace1827062dbe00ce24c737b6369bf588e748ee9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404582
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
The calls to 'getGlobalForStaticField' eagerly generate the initializer constant for the associated field. In both these cases we don't need that just to get the type of the field. Instead we can use 'translateTypeOfField' directly (same as 'getGlobalForStaticField').
This also removes unnecessary nullness from the type when the field is lazily instantiated. The global type may be nullable even if the field type isn't.
Change-Id: Id369e07335fc5350524a1b8b6c04f19dd94f8b8a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/401280
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
Use the TFA direct-call metadata to directly call a closure in function
invocations.
Closes#55231.
Tested: existing tests cover the new code paths, but I also added a new
test.
Change-Id: Ib5f26b10efd77570e256196b4bfb07e6bef800c0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/397260
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
Adds new names:
- Globals for static field initialization flags are named as
"$memberName initialized".
- Globals for static field values are named as "$memberName".
- Static field initialization functions are named as "$memberName
initializer".
Also documents fields of `Globals` and removes unused field
`_globalInitializers`.
Change-Id: Id54c6e679b2d028917e86184e2913bb51e6b5a97
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/396240
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
This mainly refactors code that use the `Closures` type to make it a bit
more clear what it does and how it does it, and make it more difficult
to misuse.
Changes:
- Make `CaptureFinder`, `ContextCollector`, `Closures.findCapures`,
`Closures.collectContexts`, `Closures.buildContexts` private.
It doesn't make sense to use these types outside, and the `Closures`
members need to be called together and in the right order. Make them
private and call them in the constructor.
Reduces API surface of `closures.dart` and makes it easier to use.
- Document all public members of `Closures`.
- Remove unused `ClosureRepresentation.exportSuffix`.
- In `TearOffCodeGenerator`, inline the single-use function
`generateTearOffGetter`. Makes it clear that the code is not reused
elsewhere.
- Make the type of `Types.nonNullableTypeType` more precise. Use it in
`closures.dart` instead of having a separate copy of the same thing.
- In a few places where we had function body entirely guarded with an
`if`, add an early return.
For example:
```
void f() {
if (x) { ... lots of code ... }
}
```
Becomes:
```
void f() {
if (!x) return;
... lots of code ...
}
```
Similarly do it in loop bodies.
Change-Id: Ia2a74b89ae311b8f32b9f1a4e72c51d4ea3861e8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/392942
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
Remove all references to a global `ModuleBuilder` (code like `ModuleBuilder get m => ...`). References to module builders should be more specific now, whether that be to the `mainModule` on Translator or some other module.
Technically this could remain and always refer to the mainModule but making the name more specific makes it clear there is no single ModuleBuilder anymore and code that needs to access a ModuleBuilder will need to consider which one it needs.
Change-Id: I140c4e80a131a1786fa66c93be07a622dd0756c3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381443
Reviewed-by: Martin Kustermann <kustermann@google.com>
Update intrinsic field table allocation to support importing tables across modules. The tables will always be defined in the main module but can be imported into subsequent modules.
Change-Id: I09c8f316f528fa28c02fddcf356a33583a88ef56
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381442
Reviewed-by: Martin Kustermann <kustermann@google.com>
Wasm globals serve a few purposes in the compiler such as storing static fields and closure vtables. Sometimes the access of these globals will be from a different module than the ones they're defined in. We need some indirection to be able to access them in these cross-module situations.
This change adds getter and setter functions that can be called via the StaticTable when a global needs to be accessed from a different module.
We use References to track the owning module for each global to determine if we can access it directly or not.
Change-Id: I93191c83dee1b7a47171c5808e64b071479cdeea
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381324
Reviewed-by: Martin Kustermann <kustermann@google.com>
Adds `Translator.callReference` which will be the main indirection point for calls between modules.
Any call that might need to be made across modules should go through `callReference` and this will handle checking if the call is local to the same module. If it is then it will use a normal "call" instruction, otherwise it will re-route the call through a table and "call_indirect".
`Reference` is the primary module assignment mechanism, we will generate synthetic References for anything that doesn't have one from the Kernel.
For now just maintains the current behavior of generating a `call` instruction.
Change-Id: I9a300d100bc7c27ec2aba42af367e91201dcadc3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381322
Reviewed-by: Martin Kustermann <kustermann@google.com>
Remove code similar to `m.types` from the compiler. With upcoming changes there will not be a global `ModuleBuilder` to get the `TypesBuilder` from. There will be multiple modules being built at once.
Change-Id: I42c6616f2babb28e14ad418ffd9e8e9b3f3e946e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381342
Reviewed-by: Martin Kustermann <kustermann@google.com>
- The comments around `collectImportsAndExports` call were wrong. Since
d9b5eec `wasm_builder` can only generate minimal recursive groups, and
`translateExternalType` only generates `ref null any` for references
to structs for Dart types. We don't need to treat imports and exports
specially to make sure they are in their own minimal groups.
- With the special case for imports and exports gone, we move
`collectImportsAndExports` to `FunctionCollector` and simply the
`FunctionCollector` API by making it private.
- Also remove `FunctionCollector.addExport` which has one call site, and
rename `getExport` as `getExportName` for clarity and consistency with
the rest of the code where we refer export names as `exportName`.
Change-Id: I399aa0553370d2b7983ae8cd0f89e1ef84b0f2ef
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/383323
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
For a class like this:
class A<T> {
T? value;
}
the implicit setter function (`A.value=`) has to check the argument
type.
TEST=language/covariant_field_implicit_setter_test
Change-Id: Ie990b79f631275fb0a7c88ec7d7dd3a82a784148
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381000
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Right now dart2wasm inlining abilities is restricted
- Only [CodeGenerator]s can inline things
- The [CondeGenerator]s can only inling AST targets (it takes
[Reference]).
This CL extends our `MacroAssembler` (which is just a static extension
on the `InstructionsBuilder`) to have an `invoke(CallTarget target)` method.
The new [CallTarget] abstraction allows any [CodeGenerator] (not just
the AST specific ones) to be inlined.
=> Allow all asm building code to inline things
=> Allow any [CodeGenerator] that is inline'able to be inlined
Change-Id: I0480282ee07adb8dbe5c37609c96de16535c49ab
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/378920
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>