Skip to content

Dart2JS type inference not converging in some cases #50626

Open
@biggs0125

Description

@biggs0125

Currently Dart2JS has a hardcoded limit on the number of times the type of a specific construct (member, call, etc.) can be refined. This was added as a way to bound the amount of time spent refining the type of any single construct.

During the ongoing efforts to improve performance of type inference in Dart2JS, we've tried to increase (or even remove) this hardcoded limit with the goal of regaining type accuracy lost elsewhere. This uncovered that the hardcoded limit has been hiding an issue where, given the right constraints, some types do not converge to a single value.

This can be seen in sufficiently large programs that make use of the built_value library (usually via protobufs). There is a cycle of ~6 types stemming from BuildJsonSerializers.serialize.

BuildJsonSerializers.serialize contains a call to BuildJsonSerializers._serialize which in turn contains a call back to BuildJsonSerializers.serialize. This mutual recursion on its own is fine. However, it becomes an issue when many different typed objects pass through this serializer (as can be the case when protobufs are used heavily).

At some point the type used to represent the return types of these functions accumulates too many disjoint types and our representation of unions falls back to a generic Object? flat type mask. The problem occurs when further refinement causes a type that was previously flattened to Object? to get turned back into a union type mask representation (by collapsing of some of the disjoint submasks from exact into subclass). Now because these 2 methods are mutually recursive, one has the flattened Object? type and the other has the expanded union type and their internal types are in either of these 2 states. The types then chase each other, flattening and re-expanding repeatedly.

Here is how the types propagate for a full cycle. Notice that at the same time some types are being flattened others are being expanded.

j:method(BuiltJsonSerializers.serialize)
Union(null, [exact=JSString], [subclass=JSMutableArray], [subclass=JSNumber], [subtype=bool]) -> [null|subclass=Object]

InstanceInvocation(InstanceAccessKind.Instance, this.{BuiltJsonSerializers._serialize}(transformedObject, specifiedType))
[null|subclass=Object] -> Union(null, [exact=JSString], [subclass=JSMutableArray], [subclass=JSNumber], [subtype=bool])

InstanceInvocation(InstanceAccessKind.Instance, this.{BuiltJsonSerializers.serialize}(object))
Union(null, [exact=JSString], [subclass=JSMutableArray], [subclass=JSNumber], [subtype=bool]) -> [null|subclass=Object]

local(BuiltJsonSerializers.serialize#result)
[null|subclass=Object] -> Union(null, [exact=JSString], [subclass=JSMutableArray], [subclass=JSNumber], [subtype=bool])

j:method(BuiltJsonSerializers._serialize)
Union(null, [exact=JSString], [subclass=JSMutableArray], [subclass=JSNumber], [subtype=bool]) -> [null|subclass=Object]

local(BuiltJsonSerializers.serialize#result)
[null|subclass=Object] -> Union(null, [exact=JSString], [subclass=JSMutableArray], [subclass=JSNumber], [subtype=bool])

InstanceInvocation(InstanceAccessKind.Instance, this.{BuiltJsonSerializers._serialize}(transformedObject, specifiedType))
Union(null, [exact=JSString], [subclass=JSMutableArray], [subclass=JSNumber], [subtype=bool]) -> [null|subclass=Object]

j:method(BuiltJsonSerializers.serialize)
[null|subclass=Object] -> Union(null, [exact=JSString], [subclass=JSMutableArray], [subclass=JSNumber], [subtype=bool])

local(BuiltJsonSerializers.serialize#result)
Union(null, [exact=JSString], [subclass=JSMutableArray], [subclass=JSNumber], [subtype=bool]) -> [null|subclass=Object]

InstanceInvocation(InstanceAccessKind.Instance, this.{BuiltJsonSerializers.serialize}(object))
[null|subclass=Object] -> Union(null, [exact=JSString], [subclass=JSMutableArray], [subclass=JSNumber], [subtype=bool])

local(BuiltJsonSerializers.serialize#result)
Union(null, [exact=JSString], [subclass=JSMutableArray], [subclass=JSNumber], [subtype=bool]) -> [null|subclass=Object]

j:method(BuiltJsonSerializers._serialize)
[null|subclass=Object] -> Union(null, [exact=JSString], [subclass=JSMutableArray], [subclass=JSNumber], [subtype=bool])

j:method(BuiltJsonSerializers.serialize)
Union(null, [exact=JSString], [subclass=JSMutableArray], [subclass=JSNumber], [subtype=bool]) -> [null|subclass=Object]

Metadata

Metadata

Assignees

Labels

area-web-jsIssues related to JavaScript support for Dart Web, including DDC, dart2js, and JS interop.dart2js-inferenceweb-dart2js

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions