-
Notifications
You must be signed in to change notification settings - Fork 1.7k
inferred types not treated the same as explicit type by deferred loading #35311
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
My proposal right now is to go with (3) Turns out that there was also a bug in the old implementation that prevented constructor elements from being treated as deferred when accessed directly via a deferred import. Going with a sound approach that fixes this bug seems to have a positive effect on code-split for some large customer apps (0.5% better!). Using an unsound approach, for example ignoring return types in closures, improves the result in that app by maybe 0.05%, so it doesn't appear to be worthwhile. |
fixed by 70e1517 |
…ting. On large apps this cuts down the time spent in the deferred loading algorithm by two thirds. To address issue #35311, this CL keeps things sound and may load in the main unit more code. On some large apps, it appears the effect of this change is not that measurable, and we even see an improvement. I'm still validating the data, but I believe this is in part because there was a different bug in the previous algorithm: constructors were never deferred because we were looking for the constructor-name, usually '', instead of the enclosing class name. My current data is that, the main unit of some large app shows: old algorithm: 13,213,191 sound algorithm: 13,150,145 unsound algorithm*: 13,147,282 fixed old algorithm: 13,146,509 * ignoring return type of closures Change-Id: I7d3e525393ef38979b26051b4d354fc1001560af Reviewed-on: https://dart-review.googlesource.com/c/85725 Commit-Queue: Sigmund Cherem <[email protected]> Reviewed-by: Johnni Winther <[email protected]>
…or splitting." This reverts commit 70e1517. Reason for revert: saw unexpected regression on acx-gallery as well. I'd like to look in more detail and reland if it is justified. Original change's description: > Record deferred accesses in kernel world impacts and use it for splitting. > > On large apps this cuts down the time spent in the deferred loading algorithm by > two thirds. To address issue #35311, this CL keeps things sound and may load in > the main unit more code. > > On some large apps, it appears the effect of this change is not that measurable, > and we even see an improvement. I'm still validating the data, but I believe > this is in part because there was a different bug > in the previous algorithm: constructors were never deferred > because we were looking for the constructor-name, usually '', instead of the > enclosing class name. > > My current data is that, the main unit of some large app shows: > old algorithm: 13,213,191 > sound algorithm: 13,150,145 > unsound algorithm*: 13,147,282 > fixed old algorithm: 13,146,509 > > * ignoring return type of closures > > > Change-Id: I7d3e525393ef38979b26051b4d354fc1001560af > Reviewed-on: https://dart-review.googlesource.com/c/85725 > Commit-Queue: Sigmund Cherem <[email protected]> > Reviewed-by: Johnni Winther <[email protected]> [email protected],[email protected] Change-Id: I6992279bebcc99578f94087a17d6c327b32a0876 No-Presubmit: true No-Tree-Checks: true No-Try: true Reviewed-on: https://dart-review.googlesource.com/c/86246 Reviewed-by: Sigmund Cherem <[email protected]> Commit-Queue: Sigmund Cherem <[email protected]>
I'm happy to see that (2) is the long term goal. To confirm, this would allow using a deferred type in a type signature, before the deferred library is used correct? import 'foo.dart' deferred as foo;
class FooLoader {
Future<foo.Foo> load() async {
await foo.loadLibrary();
return foo.Foo();
}
}
void main() await {
final loader = FooLoader();
final foo = await loader.load();
foo.bar(); // Invoke methods on `Foo` (not dynamic dispatch).
} I can't find the old issue, but this is aligned with the Angular team's requests for deferred loading and would make the feature much more ergonomic. I understand the desire to fix the soundness issues with the temporary approach, but our team has some concerns with the implementation (enabled by My understanding is that even if the closure is assigned to a type which disregards the return value, the inferred type still can't contain a deferred type because the closure is reified? This will be an ergonomic problem for Angular as a common pattern for defining deferred routes is import 'my_component.template.dart' deferred as my_component;
RouteDefinition(
loader: () async {
await my_component.loadLibrary();
return my_component.MyComponentNgFactory;
},
) where Is there anything we can do as the API authors to alleviate our users from having to insert an Furthermore, I tried the suggested solution of returning dynamic return my_component.MyComponentNgFactory as dynamic and it actually increased code size in our example app. However, I figured rather than cast as dynamic, it may be better to cast as a non-deferred supertype // ignore: unnecessary_cast
return my_component.MyComponentNgFactory as ComponentFactory; // implicitly a ComponentFactory<dynamic> now. Fortunately this improves (reduces) code size! Unfortunately the analyzer treats this as an unnecessary cast, which is a build error for our clients, making it necessary to also add the |
Thanks @leonsenft - we were also spending some time looking at the different use cases and are still debating what the right course should be. More and more I'm inclined to wait until we have (2) before we make the new algorithm the default and remove the soundness bug, but if by construction angular is setup to avoid this pitfall, we may be in a position to do the switch sooner. The most common place where I see this come up is the exact pattern that you showed above.
Correct once (2) is done, you'll be able to use deferred types explicitly or implicitly in variable and function declarations, even before the type is loaded. We may also reconsider to allow them in
Yikes, I honestly thought that using
The main thing to prevent inference from picking up the deferred type. One option here is changing the angular code generator to use a weaker type for the factory getter. So instead of: ComponentFactory<MyComponent> get MyComponentNgFactory => ... Would it work to remove the type parameter? ComponentFactory get MyComponentNgFactory => ... |
To be fair, it was a rather negligible code size increase, but I'm happy to see the code decrease from using a non-deferred supertype.
Unfortunately no. While the type argument doesn't matter for loading a route, it does matter for |
We revisited removing @sigmundch I believe dart2js shipped a whole new type system since we lasted discussed this. Any chance this is addressable now? |
@rakudrama re new rti impact on this. |
@leonsenft - correct! This is now unblocked with the new-rti. We are still wrapping up several aspects of null-safety in dart2js, but after that, @joshualitt is planning on taking a look at this. |
Just checking in, do we expect to potentially try to tackle/investigate this in the next few weeks? |
Not quite yet - we still have a few more NNBD tasks left (including our test migration work). |
Hi @sigmundch, checking in again. We've been talking about @jakemac53 for our own null-safety migration, and one thing that came up is we might be able to always emit null-safe annotated code, relying on the mixed-mode runtime to "do the right thing" (if a user is not migrated, they will largely ignore our annotations, and if they are, they will be enforced to some extent). This probably reduces the amount of work we need to do possibly by 1/3 or more, because we would only need to focus on emitting null-safe code, and not forking 35K+ lines of our compiler, or adding lots of ... however, that whole strategy is blocked because we currently |
@matanlurey thanks for checking in. @joshualitt is actively working on this. woohoo! Large part of the algorithm is in progress (see cl/154580). There is a bit more polish work needed and some validation to ensure that we are properly slicing the code, so we plan to start with this under a flag for a short period before we enable it internally. I didn't fully grasp how reexports are at play with this problem, though. I'm happy to chat offline if you think it could help explore other ideas that may let you avoid being blocked on this issue. |
@sigmundch I can try and create an external reproduction case for your team. I'll reach out again shortly. |
@matanlurey feel free to wait on it, though. I forgot about the internal bug, so I can dive in more into it there if needed. |
@sigmundch I am going to follow-up first, since your team has made a lot of RTI changes since. |
@joshualitt Can this be closed now? At least the code size aspect of this issue has been resolved (not sure if there are other, unresolved aspects of it). |
Yes, I think this can be closed. Thanks! |
Change-Id: Ia3069fc10800debefa74d53bd987a06ada04df50 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/160702 Reviewed-by: Sigmund Cherem <[email protected]> Commit-Queue: Joshua Litt <[email protected]>
According to the spec it is an error to use a deferred type in an expression or type: you can't use them in variable or function declarations, is/as/catch clauses, and cannot use them in generic type arguments of any form.
This is checked and reported as an error for explicit types in dartanalyzer and CFE, but it is today ignored for inferred types. Unfortunately there is code out there where this occurs. For example, some applications show cases like these:
When dart2js splits a program with these types, it ignores the error cases and allows those deferred types to be deferred. This is unsound in the presence of reified types.
To address performance problems in our current implementation of deferred loading, we are revisiting the algorithm and our changes are likely going to affect this behavior.
4 options so far:
(2) is our long term goal, but it will take time (at least a few months), (1) seems unfortunate if we are aiming to do (2). (4) could work because today's behavior is working for our users, however we are not sure if it's feasible to do after we change the algorithm implementation (in particular, some patterns we want to ignore are hard to distinguish from problematic patterns like #33046).
The text was updated successfully, but these errors were encountered: