Measured on size of e main module (baseline is we don't embed
it in application code):
* embedding before: +16.5% uncompressed / +9.1% compressed
* embedding with this CL: +4% uncompressed / +4.3% compressed
When embeddeding deferred load list information into the app
(as opposed to a separate json file) we now use a more compact
encoding.
Specifically: Instead of encoding it as an array of an array of
strings (which are module names), we encode it as an array of an
array of module ids and construct the module name from the id.
To make the array of module ids more compact we utilize the fact
that we can sort them and encode in delta encoding (i.e. instead
of absolute module ids, encode the diff between previous module
id in the list).
We put the encoded module id lists in a data section and create
`WasmArray<WasmI8>`s from them at startup. When we trigger a load
we then decode them into the list of module names.
There's more opportunity to optimize it, but it's good to do
this as a first step.
Change-Id: I293fb8879d992fc370786f6c9b258ccd27e1559b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/508980
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
The standalone target used `JSStringImpl` as the name for its string
implementation even though JavaScript isn't involved in that at all.
This was to simplify parts of the compiler which can then refer to both
classes with the same name.
Changing this in the compiler is not that complicated however, so it
makes sense to align the string implementation name with the embedder
terminology we also use for other host imports.
TEST=pkg/dart2wasm/test/ir_tests/standalone.{dart,wat}
Change-Id: I1e112c8a72bb43a7edfa73ff7205d353edc7403a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/504581
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
dart2wasm imports strings as globals for which JavaScript engines would
provide the respective values. The standalone target needs to support
all WebAssembly runtimes, so it can't rely on this mechanism.
Instead, this imports functions to convert a WebAssembly arrays of char
codes or ASCII bytes into a string. For now, these functions have to
return JS strings since the rest of the SDK relies on that. In the
future, embedders would be able to return any string implementation as
an externref.
Because calling host functions is invalid in constant contexts, string
constants can't be regular globals. For now, this uses the default
non-eager constant implementation with one initialization function per
string constant. Eventually, we should probably initialize these
strings in a WASM start function instead.
TEST=pkg/dart2wasm/test/standalone_test.dart
Change-Id: I93b7c3846fbe99daa8ffa31e452f62672b61ce4b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/495020
Reviewed-by: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@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>
The code in `generateNoSuchMethodCall` is calling the `noSuchMethod`
instance method. Doing so required duplication of logic from
normal instance invocations.
Instead we call a static method in core libraries that will perform the
instance invocation. Compiling that static method will then use the
normal logic we have for instance invocations (instead of duplicating
that logic in the code generator).
Part of https://github.com/dart-lang/sdk/issues/62639
Change-Id: Ic6a5e5bc7a87baa2039af551bd7a01b1557017a4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/488160
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Dart2wasm started using RTA to significantly speed up compilation.
However, a side effect of this is less exact type info during TFA.
In _invokeMain we conditionally use some js-interop logic when a
program's main takes arguments. On the web, most programs don't expect
any arguments so this is usually dead code. However, RTA sees
_invokeInternal is live and blindly treats all reachable classes as live
including the unused interop helper types in the other branches.
This code refactors the patching logic to make sure only the relevant
entry point is considered live.
This change uncovered a different bug that was suppressed by these
implicitly instantiated types. Dart2wasm was not marking some internally
instantiated classes as allocated in the function logic so entries in
the dispatch table were empty for those types. Instead any time we
generate the code for a constructor (which may not be reached via a
ConstructorInvocation like we were assuming before), we record the class
as allocated.
Change-Id: I556d3733c00b4a3e3455fa1ee2c90206d54c81f4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/484540
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This updates JS exception catching as discussed in #55481:
- Only catch JS exceptions when the exception type is `dynamic`,
`Object`, or an extension of `JSValue`. (nullable or not)
(Previously we also caught JS exceptions when the type is `Error`.)
- When the JS value caught in Wasm is `null` or `undefined`, box it as a
non-interop class. For compatibility with dart2js, this class is
copied from dart2js and has the same `toString` as the dart2js class.
- In other cases: box the JS values as `JSValue`. This means the value
can be passed as any of the interop types, and can be passed back to
JS without manual jsification.
Fixes#55481.
Issue: https://github.com/dart-lang/sdk/issues/55481
Change-Id: I23e73074729f740b90df2ca8b3c713fb39966556
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/479640
Reviewed-by: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
Today "dummy values" are generated per-module to stand in for things
like default parameter sentinels (where a given selector has multiple
default values for an optional parameter).
However, these values can end up crossing between modules. The logic is
set up to use ref_eq to check if an argument is one of these dummy
values. However, if one of these dummy values crosses between modules,
the passed value vs the ref_eq checked value will be different. Since
each module has its own canonical dummy value per type.
This new layout simplifies our handling of these dummy values by
treating them as Constants so that our normal module canonicalization
logic applies to them. We already have plenty of logic to canonicalize
constants across modules. This avoids the need for custom handling of
these dummy value globals.
Change-Id: Ia9c79923c788d7712b16705193ffbf3142141b5d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/480320
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
This leads to a small -0.4% size reduction of essentials main
module.
The type check optimizations in the compiler relied on the fact that
if `--minify` is on, then parameters of functions aren't used and
`wasm-opt` will remove the parameters of the function (as they are not
used) and then shrink call sites by not passing them.
This works in the one module scenario, but when enabling deferred
loading, we have multiple modules and the call site of the function may
be in a different module then the callee, which means the main module
exports the function and the deferred module imports it. That means
`wasm-opt` won't change the signature.
So this CL:
* makes `as` checker functions call `_throwErrorWithoutDetails` in `--minify`
mode
=> This makes the call sites in as checkers smaller
=> This will no longer call the `_throw*AsChecker` helper functions
across modules in `--minify` mode
* since the `_throw*AsChecker` functions won't be used across modules,
`wasm-opt` can now optimize their signature away (for the uses in the
core library)
Change-Id: I765b96c739401726c2c7bf8d9fb0996ee875aa89
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/473360
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
In essentials main module this removes 95k from code section
and adds 350k to data section. Uncompressed it increases size
by 3%. But because the data section addition compresses really
well, we see overall size reduction by -0.9%. We'll also see
faster startup, since we avoid the runtime to compile the 100kb
wasm function & execute it - instead just do a memcpy of the
data section.
In large apps the `_ModuleRtt.*` tables are too large to fit into
the global section, so they end up being lazily initialized.
For a large app we have a `_ModuleRtt.typeRowDisplacementSubstTable`
being 100 kb large wasm function.
The reason is that it initializes a `WasmArray<WasmArray<Type>>` which
is sparsely populated and nearby entries are often not the same,
requiring emitting 4 wasm instructions for every entry we fill.
Now instead of having two tables with the same layout where we use
the first to determine if two classes are related and the second
to give us the substitution, we store it in 3 tables:
* one that determins whether two classes are related
* one that stores the index of the substitution
* one that stores all unique substitutions
This shrinks the `WasmArray<WasmArray<_Type>>` table to be much
smaller (as it now holds only unique entries) and have a new
`WasmArray<WasmI16>` array which compresses really well and
who's data lives in the data segment.
Change-Id: If60af763b9739fd4e515e730a8e7be0db98beefd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/473281
Reviewed-by: Slava Egorov <vegorov@google.com>
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>
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>
Applying the --enable-deferred-loading flag causes dart2wasm to look up this function. The lookup fails if there are no deferred imports in the program because the function gets tree-shaken.
Instead make the function lookup optional so this failed lookup doesn't throw.
Change-Id: I5076b171a074f566cef864d2030d0188dcf81c21
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/461380
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This removes 4591 functions from ACX gallery main module
which translates to -43 KB / -1.6% (-3.5% code section)
We have already today a callsite guarantee that the closure call
(type, positional, named) arguments are valid arguments to the
target closure (they also have the right type).
That means the `closure.vtable.dynamicCall` entry's only purpose
is to unpack the (type, positional, named) argument arrays and call
the target.
Instead of calling the target directly (as we did so far) we now
unpack argument arrays and call the right vtable entry. This logic
can be shared amongst all closures of the same representation and
therefore leads to big reduction in wasm functions.
=> We do that in this CL.
There's two exceptions to this:
* In dynamic module scenario we don't have closed-world knowledge
of closure definitions & closure call site. There's no specific
vtable entries for positional+name combinations we could forward
to.
* In closed world scenario where there's a usage of `Function.apply`
with named arguments: We don't generate vtable entries for all
possible name combinations a closure can be called with.
So we change the closure layouter algorithm to find out if there's
a usage of `Function.apply` with named arguments.
A few tangential changes:
* Fix a bug revealed by this change: The static tearoff
instantiation constant's dynamic call entry must pass the generic
closure object when calling the generic closure.
=> The shared dynamic call entry dispatchers will now verify
(in assertion) mode the assumptions, which revealed this issue
* The closure layouter algorithm will now consider `obj.foo(a: ...)`
as a potential dynamic call site (due to call-via-field) and
therefore record the name combinations used there
=> Tested via `web/wasm/closures/dynamic_call_via_field_test`
We test the optimization by checking in 3 tests that show what
ends up in the vtables:
* `pkg/dart2wasm/test/ir_tests/dyn_closure.dart`
=> uses dynamic calls
=> dynamic call entries are "closure arguments dispatcher"
* `pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply.dart`
=> uses Function.apply without names
=> dynamic call entries are "closure arguments dispatcher"
* `pkg/dart2wasm/test/ir_tests/dyn_closure_function_apply_named.dart`
=> uses Function.apply with named arguments
=> dynamic call entries are closure specific
Issue https://github.com/dart-lang/sdk/issues/60458
Change-Id: I099984b542b05920b02596410a1bf6a08d2a0302
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/460080
Reviewed-by: Ömer Ağacan <omersa@google.com>
Currently `web/wasm/flute_stress_test` is failing on
`dart2wasm-linux-optimized-jsc` mode.
That mode runs with `--use-sdk` which makes it use `dart compile wasm`.
The reason the test is failing is because it passes both
`--enable-deferred-loading` and `--multi-module-stress-test` flags to
dart2wasm. This causes us to trigger the deferred module flow instead
of the specialized multi-module flow.
Those two flags should be independent.
Change-Id: I8cac976cd91d4e585f38632ada3062e41fce401f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/458800
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Nate Biggs <natebiggs@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>
Load ids provide a way to reduce the overhead of deferred loading. By
default deferred loading requires mapping `loadLibrary` calls to a list
of modules. This requires including (1) library uris, (2) prefix names
and (3) module names directly in the main module. With this loading
modules is easier as the loading function gets the exact filename.
Load ids provide an alternative approach where the compiler emits a
separate file mapping a load ID to the module set required for that ID.
An app could store this mapping on the server allowing the frontend to
include only the load ID in its request and have the server figure out
which modules to send back.
Internal serving infra uses module sets like this so this change allows
easier integration into that tooling. Dart2js already supports emitting
this deferred mapping JSON and internal infra is using that today.
Other changes include:
- Run the deferred loading transformer after TFA. This will exclude unused libraries in the resulting deferred loading map. Mark deferred helpers as entry points so that they don't get tree-shaken.
- Some changes to naming conventions of JS helpers.
- Use filename as module name to simplify JS helpers
Change-Id: I5e2f5e374de77c87095d08bfff6534506cb10652
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/454240
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
This reverts commit b65435017d
This reverts commit 2254755d65
The switch-case optimizations introduced a bug that is surfaced when
running the dart2wasm self-compilation test in [0]. With that test
running
```
% python3 tools/test.py -n unittest-mac pkg/dart2wasm/test/self_compile_test
```
will fail with
```
...
Exception in StaticInvocation at file:///FakeSdkRoot/sdk/lib/_internal/wasm/lib/boxed_double.dart:346:29
Bad state: Unhandled WasmArray intrinsic: StaticIntrinsic.wasmArrayIndex
at module0.Error._throwWithCurrentStackTrace (wasm://wasm/module0-0121906a:wasm-function[160]:0xd0d76)
at module0.AstCodeGenerator.visitStaticInvocation (checked entry) (wasm://wasm/module0-0121906a:wasm-function[6304]:0x1668e5)
at module0._TreeVisitor1Default&Object&TreeVisitor1DefaultMixin&ExpressionVisitor1DefaultMixin.visitStaticInvocation (checked entry) (wasm://wasm/module0-0121906a:wasm-function[6306]:0x167b61)
at module0.StaticInvocation.accept1 (wasm://wasm/module0-0121906a:wasm-function[6297]:0x165f5f)
at module0.AstCodeGenerator.translateExpression (wasm://wasm/module0-0121906a:wasm-function[1742]:0xfcd1b)
at module0.AstCodeGenerator.visitEqualsCall (checked entry) (wasm://wasm/module0-0121906a:wasm-function[6960]:0x1777e4)
at module0.EqualsCall.accept1 (wasm://wasm/module0-0121906a:wasm-function[6944]:0x1773b4)
at module0.AstCodeGenerator.translateExpression (wasm://wasm/module0-0121906a:wasm-function[1742]:0xfcd1b)
/Users/kustermann/src/dart-sdk/sdk/pkg/dart2wasm/bin/run_wasm.js:346: [object WebAssembly.Exception]
```
The switch-case in the intrinsifier seems to be miscompiled.
Change-Id: I3bfe8887fa133573379c32d52e15769a4e6db43e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/443082
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
Often switches over enums are exhaustive or nearly exhaustive. The current generator uses identity for the enum values which requires iterating over each value to compare it to the test expression. So each time the switch body is entered is an O(n) operation (where is n is the # of case expressions).
This now generates a br_table using the index of the enum and jumps directly to the correct clause, effectively an O(1) operation.
A similar approach is taken for switches over an integer range. If the range of case expresison values is close in size to the # of values, it is advantageous to normalize the range around 0 and treat the values themselves as table indices.
This approach will also save code size when the index range is dense as the br_table is more compact than the identity/br_if checks. For sparse ranges this may produce a bit more code though only on the order of a few bytes per value in the range.
I've added a denseness heuristic to decide when to revert to the current strategy to avoid the code size penalty.
Golem benchmark: https://golem.corp.goog/Comparison?repository=dart#targetA%3Ddart2wasm-O2-d8%3BmachineTypeA%3Dlinux-x64%3BrevisionA%3D117402%3BpatchA%3Dnatebiggs-dart2wasm-switch-tables3%3BtargetB%3Ddart2wasm-O2-d8%3BmachineTypeB%3Dlinux-x64%3BrevisionB%3D117401%3BpatchB%3DNone
See 100% improvement in SwitchFSM.int and 90% improvement in SwitchFSM.enum.
Change-Id: Ie29e8fd59ef6235044ba5b4a4af04023d702ce57
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/441760
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
* Make globals for constants have names which allows reading code
easier as the name of the global for a constant will have
the type/kind in it
* Make lazy initializer functions for globals have explicit names in
most cases, which avoids arbitrary length names for list constants
* Both globals and lazy initializer functions will get names
that include C<number> (for ease grep'ping navigating in wat files)
and a descriptive name that includes the type and/or value of
the constant.
* Make list constant implementation lower to wasm array constants,
just as we do for map constants. This avoids code duplication
and allows canonicalization of the wasm arrays.
Closes https://github.com/dart-lang/sdk/issues/55409
Change-Id: I7ccbdd3d44cf916bf147fdd1053ed52ddd1c0dc5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/435420
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
In `async` functions, instead of completing the function's `Future` via
`Completer`, do it directly.
This should be slightly more efficient as we eliminate a layer of
indirection when completing.
Move reading the `_future` field of `_AsyncSuspendState` to the
completion functions, to avoid adding a `struct.get`s at each call site.
In ACX demo, makes the final binary 0.1% smaller (13,689 bytes).
Fixes#60719.
Issue: https://github.com/dart-lang/sdk/issues/60719
CoreLibraryReviewExempt: Wasm-specific change.
Change-Id: I27b376eb2fb9c3705ee930fb33b06d9accfd14b8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/429000
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
Modifies the serialization strategy used for dynamic modules. Emits 2 dill files, main.dill and main.opt.dill.
main.dill serialized before TFA runs and is used by the CFE to compile the dynamic module faster (it doesn't have to recompile the main module libraries from source).
main.opt.dill is serialized at the end of the compilation of the main module and contains a copy of all the main module libraries after TFA. This dill is used by dart2wasm to produce consistent references into the main module.
Also emit new record classes into a distinct library so that all downstream code will appropriately emit the new code without special logic.
Change-Id: Idf7a87d2014dad513c753330743f753312aa472f
Tested: Just updating signature, no testing needed.
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/422342
Reviewed-by: Martin Kustermann <kustermann@google.com>
In V8, the only way to pass a Wasm integer or float to JS without
allocation is by passing it as a 31-bit integer.
This can be done by:
1. Passing as `i32`. If the integer fits into 31 bits it's passed
without allocation.
2. Passing as externalized `i31ref`.
(1) requires importing the JS function with different signatures: for
each `int` argument we would need a signature with the `i32` as the Wasm
argument type, and another with `externref` (or `f64` if we want to pass
large integers as `f64`).
This is not feasible as with a JS function with N `int` arguments we
would need `2^N` imports. So we implement (2): we import each interop
function with one signature, passing `externref` as the argument, as
before. When the number fits into 31 bits we convert it to an `i31ref`
and externalize it. Otherwise we convert the number to `externref` as
before, by calling the JS function `(o) => o` imported with type `[f64]
-> [externref]`.
New benchmark checks `int` passing for small (31 bit) and large (larger
than 31 bit) integers. Results before:
WasmJSInterop.call.void.1ArgsSmi(RunTimeRaw): 0.020 ns.
WasmJSInterop.call.void.1ArgsInt(RunTimeRaw): 0.018 ns.
After:
WasmJSInterop.call.void.1ArgsSmi(RunTimeRaw): 0.014 ns.
WasmJSInterop.call.void.1ArgsInt(RunTimeRaw): 0.018 ns.
Issue: https://github.com/dart-lang/sdk/issues/60357
Change-Id: I749001e0e7e9784114415439298c2f3e0fb974b3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/419880
Commit-Queue: Ömer Ağacan <omersa@google.com>
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>
Currently we have 3 different string types (JS Strings, OneByteString
and TwoByteString)s. There's some advantages to this, mainly that
if strings are used purely inside Dart we have more control over
optimizing them. But it does come with some issues
* Operations on mixture of strings are slow
* We get JS strings from outside (in DevTools e.g. websocket messages)
* Any kind of DOM interaction requires copying strings
* Regular expression matches can result in O(N*N) instead of O(N)
* Encoding of string literals/constants is terrible, high size overhead
* ...
Now that there's a standardized way to access JS strings (via
the `js-string` builtin spec) and this standard is finalized and
enabled in Chrome & Firefox it makes sense for us to switch to it.
It reduces app size:
* Smaller size: hello world -25%, flute -5.5%
* Faster startup
The performance changes are nuanced, some workloads will improve
significantly, some workloads will regress.
Improvements will come especially in cases where
strings are concatenated (due to JS not actually allocating new
strings in this case). That impacts e.g. string interpolations,
string buffer, json-to-string encoding, ...
Regressions will come especially for cases where we have to
construct strings from bytes (e.g. in utf8 decoder, utf8+json
decoder) - mainly due to having to go through an intermediary
`WasmArray<WasmI16>` to allocate strings. Also in cases where we
access individual char codes from the strings.
There's some follow-up improvements we can do, but it's better to
not iterate on this CL even longer but get it landed.
This CL will make the benchmarking system use
`--require-js-string-builtin` as well as most of test CI
(in `pkg/dart2wasm/tool/compile_benchmark`)
Though we run some configurations via overriding with
`--no-require-js-string-builtin`
(in `tools/bots/test_matrix.json`)
Issue https://github.com/flutter/flutter/issues/159400#issuecomment-2538593980
Issue https://github.com/dart-lang/sdk/issues/59699
TEST=ci
Change-Id: I238ac65efe092de569da870f23134f889ac929f9
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/392903
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
Commit-Queue: Martin Kustermann <kustermann@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>
In a few places we create IntConstants to represent constant fields that are typed as WasmI32. If these are nested within other constants we end up hitting ConstantCreator.visitIntConstant for them. This always generates a global for a BoxedInt constant, whether it's used or not.
The global we create goes unused because in constant intiailizer for the outer constant we use the wasmI32 value directly.
At -O0 this reduced the wasm binary size by ~24k for a simple Flutter app. Binaryen mostly treeshakes these unused globals but I still see a slight improvement even with binaryen running.
Change-Id: I89b8392138269d7322196fa415e56e3062a46088
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/408121
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Dart does not incorporate immutability into it's type system.
Though the WasmGC type system does have such a concept:
* mutable wasm arrays: they are invariant in the element type
* immutable wasm arrays: they are covariant in the element type
Currently we use the mutable wasm array types for e.g.
const foo = const WasmArray<WasmI32>.literal([0]);
which will be compiled to
(type $Array<WasmI32> () (array (field (mut i32))))
(global $globalFoo
(ref $Array<WasmI32>)
(i32.const 0)
(array.new_fixed $Array<WasmI32> 1
)
=> Notice the **mut** in `array (field (mut i32))`
This CL introduces a `ImmutableWasmArray` which is analogous to
`WasmArray`. The array supports only reading methods since the
contents of the array cannot be modified.
Future CLs will make use of this new array type in various places.
=> This will allow binaryen to optimize things better as it knows the
array contents cannot change.
=> So loads from a (final) global with immutable contents can be
folded away at compile time if the index is known at compile-time.
NOTE: The `WasmArray`& `ImmutableArray` classes are not related to
each other. The reason is that in WasmGC a mutable array isn't a
subtype of a immutable array.
Change-Id: I3a764b5aaa3ac47827332f3064035133ab01f1b2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/392900
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This is a reland of commit fca5417d49
- Patchset 1 is the reland.
- Patchset 2 adds a regression test and fixes the bug.
Also tested engine chrome-dart2wasm-html-engine,chrome-dart2wasm-html-html,chrome-dart2wasm-html-ui,chrome-dart2wasm-canvaskit-canvaskit,chrome-dart2wasm-canvaskit-ui,chrome-dart2wasm-skwasm-ui tests manually.
Original change's description:
> Reapply "[dart2wasm] Allocate boxed bools once" and "[dart2wasm] Box `Pointer` values obtained from FFI calls"
>
> This reverts commit a4e9775a99.
>
> - Patchset 1 reverts the revert.
>
> - Patchset 2 adds regression tests. These tests fail.
>
> - Patchset 3 and the rest fixes the bug in `_loadPointer` and
> `_storePointer` intrinsics. Also adds some inline annotations to
> reduce the noise in unoptimized builds.
>
> Tested: updated test web/wasm/ffi/ffi_native_test
> Change-Id: I748a8aa8ff7cc663b1980d2bfab0d0da9f369e3b
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384760
> Reviewed-by: Martin Kustermann <kustermann@google.com>
> Commit-Queue: Ömer Ağacan <omersa@google.com>
Tested: web/wasm/ffi/ffi_native_test updated. Manual run of engine chrome-dart2wasm-html-engine,chrome-dart2wasm-html-html,chrome-dart2wasm-html-ui,chrome-dart2wasm-canvaskit-canvaskit,chrome-dart2wasm-canvaskit-ui,chrome-dart2wasm-skwasm-ui tests.
Change-Id: Ifcd5156b8a03228119b2146edcfc56db8ac6273b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384843
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
This reverts commit a4e9775a99.
- Patchset 1 reverts the revert.
- Patchset 2 adds regression tests. These tests fail.
- Patchset 3 and the rest fixes the bug in `_loadPointer` and
`_storePointer` intrinsics. Also adds some inline annotations to
reduce the noise in unoptimized builds.
Tested: updated test web/wasm/ffi/ffi_native_test
Change-Id: I748a8aa8ff7cc663b1980d2bfab0d0da9f369e3b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384760
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
For consistency with rest of the standard library types (numbers types,
strings, collections like maps, sets, lists, ...) move boxed bool type
to its own library.
Change-Id: I30ea7dbb0957115a0614609ec4433f6119164939
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/382360
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
Currently `Pointer` argument and return types of `FfiNative` functions
are translated to Wasm as `i32`.
This causes problems when we need to box those `i32` values as we don't
track which Dart types Wasm types come from when converting a Dart type
like `Pointer` to a Wasm type like `i32`.
Currently this works somewhat accidentally. All `i32`s are boxed as
`BoxedBool`, including `i32`s that represent `Pointer`s.
This breaks when we need to get the type parameter of a `Pointer` (e.g.
in a `is` or `as` check), but more importantly it means that we can't
cache boxed `true` and `false` values and return those cached values
when converting an `i32` to a boxed type as we don't know whether the
`i32` represents a bool or pointer.
Ideally we would have some kind of intermediate layer between
`wasm_builder` and dart2wasm that allows types like "a Wasm i32
representing an unboxed T" (for some T).
Alternatively we could attach extra information to `ValueType`s, for
example using expandos or maybe by adding a `dynamic` field to the base
class.
However it's unclear whether it's worth doing a major refactoring, when
a simpler alternative exist: we box `Pointer` values obtained from an
import in the Dart wrapper for the imported function.
VM already boxes `Pointer`s, so the performance should be acceptable.
This CL implements this simpler alternative of boxing `Pointer` values.
Tested: web/wasm/ffi/ffi_native_test updated.
Change-Id: I02e5c07fdb021a7b51ed5db2a44123b1608c9aad
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/383325
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
This is the final CL for deferred loading. It wires up the library-module analysis logic to the compiler. With all the Translator module predicates implemented, code should now be generated in separate modules (assuming the flag is enabled).
This also handles the module naming scheme. For an invocation of dart2wasm like `dart2wasm main.dart out.wasm` this will produce files like `out.mjs, out.wasm, out_module1.wasm, out_module2.wasm, ...`. `out.wasm` is the main module that gets loaded on initialization. When the flag is disabled this will always be the only output.
If the flag is disabled then the `_importMapping` in `deferred.dart` will be empty and we will default to the same behavior as today which will be to just return an empty `Future`. When enabled, `loadLibrary` will fetch and instantiate the new module(s) before proceeding.
Change-Id: I0dd136c0af61b916be2a24b3d79052ff1b786b52
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/380440
Reviewed-by: Martin Kustermann <kustermann@google.com>
Up to Patchset 4 reverts commit 36fb02deca
Remaining Patchsets implement specialized support in is/as check
implementation to handle cases where only a null check is required. The
most common case in iterators:
class <...>Iterator<T> {
T? _current;
T get current => _current as T;
}
This ensures we're never emitting the general subtype checking code as
this only requres checking whether the object is non-`null` or whether
the destination type is declared nullable.
The asymmetry between `is` and `as` checks comes due to the fact that
the `as` checks have to (for the slow case) materialize a type object
for a nice error message.
Issue https://github.com/dart-lang/sdk/issues/55516
Change-Id: Ie4a845816ec4eda37a5a2e78cac0aeda0e411abd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381482
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This opens possibility for other core libraries to access implementation
details of compact hash maps/sets and call special "core-library-only"
methods.
TEST=ci
CoreLibraryReviewExempt: VM only library change
Change-Id: I1d7524932c34e6fbe2428853dd547d58bef2d061
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/379840
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
The specialized is/as checker functions are currently only used
for cases where the type to check against doesn't require checking
argument types.
This CL will extend that for cases where we do have to check
argument types, but we know that there's no need to substitute
type arguments. This saves the RTT system from searching for
the type argument substitution value.
Cases where this applies is for example
```
class Base<T> {}
class Sub<T> extends Base<T> {}
class Sub2<T> extends Base<T> {}
class Sub3<T> extends Base<T> {}
final l = <Base<Object>>[Sub<int>(), Sub2<String>(), Sub3<double>()];
foo<T>() {
final Base<Object> b = l[1];
if (b is Base<T>) { ... }
if (b is Base<String>) { ... }
}
```
Here we know that all classes that directly or indirectly implement
`Base<T>` just pass their type parameter up the hierarchy.
=> There's no need to translate from type parameter array of subclass
to that of the super class.
We'll generate optimized is/as helpers for this case now, e.g.
```
func foo {
...
local.get $var0
global.get $global40
call $<obj> is Base<T0>
...
}
func $<obj> is Base<T0> {
// Check whether obj is in class-id range of Base subtypes
i32.const 0
local.get $var0
struct.get $Base $field0
i32.const 107
i32.sub
i32.const 3
i32.ge_u
br_if $label0
drop
// Call Object._getTypeArguments()
i32.const 0
local.get $var0
local.get $var0
struct.get $Base $field0
i32.const 338
i32.add
call_indirect (param (ref $#Top)) (result (ref $Array<_Type>))
// Check whether first type argument is subtype of T0
i32.const 0
array.get $Array<_Type>
ref.null none
local.get $var1
ref.null none
call $_TypeUniverse.isSubtype
i32.const 1
i32.ne
br_if $label0
drop
i32.const 1
}
```
It has overall negligible code size impact (~ 0.1% increase)
Issue https://github.com/dart-lang/sdk/issues/55516
Change-Id: Ic151269ad1b4a1456782b387beb4d646786ac493
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/374681
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This allows injecting internally public members int and double members
to the libraries and it's consistent with how we've been restructuring
the dart2wasm standard libraries.
Tested: existing tests
Change-Id: I5261f3ab78667de93b4d7583d99bb9ae151a6cb8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/373241
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
This WasmArray-backed list classes to `dart:_list`, similar to
`dart:_string` and `dart:_typed_data`
This will allow having internally-public list members like unchecked
getters and setters that we will use in the rest of the standard
library.
This CL doesn't add anything new, just renames things.
- `dart.core._ListBase` -> `dart._list.WasmListBase`
This is to avoid confusion with `dart.core.ListBase` (defined by Dart
standard library), and also to be consistent with `WasmTypedDataBase`
and `WasmStringBase`.
- `dart.core._List` -> `dart._list.ModifiableFixedLengthList`
Similar to the above, to avoid confusion with `dart.core.List`.
- `GrowableList.ofOther` -> `GrowableList.fromIterable`
`ofOther` sounds like it should make a growable list from another
growable list, but the type is more general than that. To reflect what
it actually does we call it `fromIterable`.
Tested: Existing tests.
Change-Id: I24398765e1b0d549fc70b03ba94161479c5fc54c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/372622
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This moves `JSStringImpl` class from `dart:_js_types` to `dart:_string`.
This allows implementing a common base class with unchecked operations
to all string classes as internal methods (so users won't be able to
call them via `dyanmic`), and extension methods to call these unchecked
methods in libraries like `dart:convert`.
Uses of these methods are introduced in
https://dart-review.googlesource.com/c/sdk/+/372443.
Change-Id: Ie4cfe778654c42d62bc4a90391fe349fa783a42c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/372442
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>