-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Implement function canonicalization corner cases #46485
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
@eernstg @lrhn It is not clear to me from this issue (and from the language discussion) exactly what is intended to be implemented, and where. In particular, the discussion seems to touch on four separate things:
I believe that 1 and 2 are probably properly handled by the CFE, is that correct? cc @johnniwinther 3 must be handled at runtime by the backends, and I think is well-specified (are there failures here?) 4 must be handled at runtime by the backends, but I don't think we have a clear specification of what is expected. Specifically:
I don't think the tests landed cover 4. Per discussion, in the language meeting, this change should be gated on ease of implementation in the tools. cc @fishythefish @nshahan @sigmundch @a-siva @alexmarkov @johnniwinther @stefantsov |
In the CFE we previously canonicalized static tear offs even in non-const context. Recently I added canonicalization of instantiated static tear offs in non-const context: https://dart-review.googlesource.com/c/sdk/+/204960 . This will also handle constructor tear offs when support for these is landed. |
There's also local functions, which is a third category from static/top and instance methods. |
tl;dr We may need to add a short paragraph to the NNBD spec, saying that we use the equality of reified types to compare the actual type arguments of function objects with generic instantiation at run time rd;lt @leafpetersen wrote:
Right, I was relying on the existing specification to determine the desired semantics, and on the tests to point out known cases where the current behavior is different. So the actual text in this issue makes no attempt to spell it out. I'm not 100% sure about the meaning of the four categories:
I assume this would target those cases where we tear off a top-level function or a static method (aka static functions), and there is no generic instantiation.
Does this one cover instance method tearoffs without generic instantiation plus static function tearoffs with generic instantiation? Why would we want to consider those as related?
This could be instance method tearoffs without generic instantiation, but it's confusing that it overlaps with the previous one.
This could be instance method tearoffs with generic instantiation. About the CFE and the static function tearoffs, with and without generic instantiation: The tests landed in https://dart-review.googlesource.com/c/sdk/+/202243 are all succeeding with cfe-strong-linux (and other, pure CFE configs). Of course, these configurations don't run the code. The VM fails in several cases, and this may or may not involve anything in the CFE:
The backends must be involved in the static function tearoffs in the case where there is a generic instantiation and the type argument is not a constant type expression (that is, cases involving I believe the above list shows that the CFE-and-or-the-vm do not canonicalize constant expressions when they do not occur in a constant context (they occur as
No plain test failures. When I comment out all tests involving generic instantiation then the landed tests all succeed with dart (dartk-strong-linux-release-x64), dart2js (dart2js-hostasserts-strong-linux-x64-d8), and dartanalyzer (analyzer-asserts-strong-linux); dartdevk fails an inequality test (that's a crazy case, reported separately), but when that's commented out it also succeeds.
True, the language specification just says that the actual type arguments used in the generic instantiation must be the 'same types'. We actually specify that So the question is whether it's appropriate to use the rule for equality of reified types in the case where we are comparing actual type arguments of function objects (nobody says that they are 'reified types'). Anyway, it seems reasonable to go that way, so I'd recommend that we add a short section to the NNBD spec to say so.
Agreed, and this also matches what we have specified already. |
tl;dr Local function tearoff identity/equality is unspecified rd;lt @lrhn mentioned local functions: True, they are not covered by https://dart-review.googlesource.com/c/sdk/+/205081 nor https://dart-review.googlesource.com/c/sdk/+/202243. The language specification explicitly says that no guarantees are given about the equality and/or identity of a function object obtained by closurization of a local function with generic instantiation. It says nothing about the plain case (without generic instantiation). I made a note that we should add that. In any case, we should presumably only have tests concerned with these properties if we give some guarantees (other than the implied " If we commit to a specific implementation (which may well be the one that we're using), namely that a local function works like a final local variable whose initializing expression is a function literal, then we can promise that two local tearoffs are identical (and hence equal) if and only if they are obtained during the same execution of the enclosing function. However, if we wish to allow optimizations like lifting out local functions that do not use their innermost enclosing scopes (e.g., all the way to the top level, if possible), then we may be able to obtain substantial performance benefits, but they would then be identical across invocations of the enclosing function. So do we want to give any guarantees about identity and/or equality for local functions? How often is that used? How important are those optimizations? Would it be OK to promise equality/identity per invocation, and actually have identity in all cases as the actual behavior with some local functions? (After all, we don't have to specify that |
Typo, should have said "non-instance method, implicitly instantiated".
See previous, I believe this is now unambiguous.
That is what it says, yes... :)
Gotcha, so 1 and 2 must be handled by a mix of the CFE and the backends.
|
The CL I made only enables the eager canonicalization for the |
Yes, I'd say that this would be considered as a set of bug fixes, not an experiment. |
It looks like dart2js had no issues on https://dart-review.googlesource.com/c/sdk/+/202243. I investigated the failures on https://dart-review.googlesource.com/c/sdk/+/205081 and created https://dart-review.googlesource.com/c/sdk/+/205764 in response. Pending review/hammering out any implementation details, this should address all of the known corner cases for dart2js. |
Sounds good! This seems to be a strong hint that, for dart2js, it is not unbearably expensive (in terms of run-time performance, or in terms of implementation effort) to achieve the semantics that makes even the dynamic cases equal. (This issue is about canonicalization, dart-lang/language#1712 and https://dart-review.googlesource.com/c/sdk/+/205081 are concerned with equality.) |
Uh oh!
There was an error while loading. Please reload this page.
The language team decided that function objects obtained from closurization or generic function instantiation of constant expressions should be canonicalized, cf. dart-lang/language#1686.
Implementations have actually done that already for many years now, except that there are some corner cases where canonicalization does not occur.
These cases are detected by failures in the tests added by https://dart-review.googlesource.com/c/sdk/+/202243.
Subtasks:
The text was updated successfully, but these errors were encountered: