[dart2wasm] Use default value sentinel when member body unreachable
This issue was partially fixed by: https://dart-review.googlesource.com/c/sdk/+/428541 However, this only looked at the case of a member in an abstract class that itself was made abstract if it was unused. TFA does not make the member abstract if the enclosing class is not abstract: pkg/vm/lib/transformations/type_flow/transformer.dart#L2289 Instead we can use the unreachable metadata to identify these unreachable body members (that have default values cleared): pkg/vm/lib/transformations/type_flow/transformer.dart#L736 Attached test fails prior to this fix. Bug: https://github.com/dart-lang/sdk/issues/62273 Change-Id: I1ccb818ec41bbe805ee828be0b96ba23e73beae4 Tested: Introduced unit test. Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/469280 Reviewed-by: Alexander Markov <alexmarkov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Nate Biggs <natebiggs@google.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import 'dart:math' show min;
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:vm/metadata/procedure_attributes.dart';
|
||||
import 'package:vm/metadata/table_selector.dart';
|
||||
import 'package:vm/metadata/unreachable.dart';
|
||||
import 'package:wasm_builder/wasm_builder.dart' as w;
|
||||
|
||||
import 'class_info.dart';
|
||||
@@ -131,7 +132,11 @@ class SelectorInfo {
|
||||
final references = source.readList(source.readReference);
|
||||
|
||||
final paramInfo = _parameterInfoFromReferences(
|
||||
references, useSentinelForOptionalParameters);
|
||||
source.component
|
||||
.metadata[UnreachableNodeMetadataRepository.repositoryTag]
|
||||
as UnreachableNodeMetadataRepository,
|
||||
references,
|
||||
useSentinelForOptionalParameters);
|
||||
return SelectorInfo._(dispatchTable, id, name, callCount,
|
||||
isSetter: isSetter)
|
||||
..useMultipleEntryPoints = useMultipleEntryPoints
|
||||
@@ -747,7 +752,9 @@ class DispatchTable {
|
||||
selector.useMultipleEntryPoints = false;
|
||||
selector._useSentinelForOptionalParameters = true;
|
||||
selector.paramInfo = _parameterInfoFromReferences(
|
||||
selector._references, selector._useSentinelForOptionalParameters);
|
||||
translator.unreachableMetadata,
|
||||
selector._references,
|
||||
selector._useSentinelForOptionalParameters);
|
||||
} else {
|
||||
// Will be initialized in the `selectorTargets.forEach()` below.
|
||||
}
|
||||
@@ -814,7 +821,9 @@ class DispatchTable {
|
||||
selector._useSentinelForOptionalParameters = true;
|
||||
}
|
||||
selector.paramInfo = _parameterInfoFromReferences(
|
||||
selector._references, selector._useSentinelForOptionalParameters);
|
||||
translator.unreachableMetadata,
|
||||
selector._references,
|
||||
selector._useSentinelForOptionalParameters);
|
||||
|
||||
// Split up [ranges] into those that are statically dispatched to and
|
||||
// those are used via dispatch table.
|
||||
@@ -1076,15 +1085,26 @@ bool _isUsedViaDispatchTableCall(SelectorInfo selector) {
|
||||
}
|
||||
|
||||
ParameterInfo _parameterInfoFromReferences(
|
||||
List<Reference> references, bool useDefaultValueSentinel) {
|
||||
UnreachableNodeMetadataRepository unreachableMetadata,
|
||||
List<Reference> references,
|
||||
bool useDefaultValueSentinel) {
|
||||
final unreachableNodeMapping = unreachableMetadata.mapping;
|
||||
// We know all target implementations (closed world) if all of them use
|
||||
// the same default value for optionals, we can make the caller pass it.
|
||||
final first = references.first;
|
||||
final firstMember = first.asMember;
|
||||
final paramInfo = ParameterInfo.fromMember(
|
||||
first, useDefaultValueSentinel || first.asMember.isAbstract);
|
||||
first,
|
||||
useDefaultValueSentinel ||
|
||||
firstMember.isAbstract ||
|
||||
unreachableNodeMapping[firstMember] != null);
|
||||
for (final target in references.skip(1)) {
|
||||
final targetMember = target.asMember;
|
||||
paramInfo.merge(ParameterInfo.fromMember(
|
||||
target, useDefaultValueSentinel || target.asMember.isAbstract));
|
||||
target,
|
||||
useDefaultValueSentinel ||
|
||||
targetMember.isAbstract ||
|
||||
unreachableNodeMapping[targetMember] != null));
|
||||
}
|
||||
return paramInfo;
|
||||
}
|
||||
|
||||
@@ -178,11 +178,12 @@ class DataSerializer {
|
||||
}
|
||||
|
||||
class DataDeserializer {
|
||||
final Component component;
|
||||
final _BinaryDataSource _source;
|
||||
final _IdToEntityMapper _mapper;
|
||||
final List<String> _stringIndexer = [];
|
||||
|
||||
DataDeserializer(Uint8List bytes, Component component)
|
||||
DataDeserializer(Uint8List bytes, this.component)
|
||||
: _source = _BinaryDataSource(bytes),
|
||||
_mapper = _IdToEntityMapper(component);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import 'package:vm/metadata/direct_call.dart';
|
||||
import 'package:vm/metadata/inferred_type.dart';
|
||||
import 'package:vm/metadata/procedure_attributes.dart';
|
||||
import 'package:vm/metadata/unboxing_info.dart';
|
||||
import 'package:vm/metadata/unreachable.dart';
|
||||
import 'package:wasm_builder/wasm_builder.dart' as w;
|
||||
|
||||
import 'class_info.dart';
|
||||
@@ -184,6 +185,9 @@ class Translator with KernelNodes {
|
||||
(component.metadata[ProcedureAttributesMetadataRepository.repositoryTag]
|
||||
as ProcedureAttributesMetadataRepository)
|
||||
.mapping;
|
||||
late final UnreachableNodeMetadataRepository unreachableMetadata =
|
||||
component.metadata[UnreachableNodeMetadataRepository.repositoryTag]
|
||||
as UnreachableNodeMetadataRepository;
|
||||
|
||||
// Other parts of the global compiler state.
|
||||
@override
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'package:vm/metadata/procedure_attributes.dart'
|
||||
show ProcedureAttributesMetadataRepository;
|
||||
import 'package:vm/metadata/table_selector.dart'
|
||||
show TableSelectorMetadataRepository;
|
||||
import 'package:vm/metadata/unreachable.dart';
|
||||
|
||||
final bool compilerAssertsEnabled = (() {
|
||||
bool compilerAsserts = false;
|
||||
@@ -104,6 +105,7 @@ String intToMinString(int i) {
|
||||
|
||||
Component createEmptyComponent() {
|
||||
return Component()
|
||||
..addMetadataRepository(UnreachableNodeMetadataRepository())
|
||||
..addMetadataRepository(ProcedureAttributesMetadataRepository())
|
||||
..addMetadataRepository(TableSelectorMetadataRepository())
|
||||
..addMetadataRepository(DirectCallMetadataRepository())
|
||||
|
||||
@@ -19,8 +19,10 @@ class UnreachableNode {
|
||||
/// Repository for [UnreachableNode].
|
||||
class UnreachableNodeMetadataRepository
|
||||
extends MetadataRepository<UnreachableNode> {
|
||||
static const repositoryTag = 'vm.unreachable.metadata';
|
||||
|
||||
@override
|
||||
final String tag = 'vm.unreachable.metadata';
|
||||
final String tag = repositoryTag;
|
||||
|
||||
@override
|
||||
final Map<TreeNode, UnreachableNode> mapping = <TreeNode, UnreachableNode>{};
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
class A {
|
||||
void foo({String s = 'hi'}) {
|
||||
print(s);
|
||||
}
|
||||
}
|
||||
|
||||
class B extends A {
|
||||
@override
|
||||
void foo({String s = 'there'}) {
|
||||
print(s);
|
||||
}
|
||||
}
|
||||
|
||||
A getA(int x) {
|
||||
if (x < 10) {
|
||||
return B();
|
||||
} else {
|
||||
// A is instantiated but the type cannot flow into a 'foo' call so A.foo's
|
||||
// body is not reachable.
|
||||
throw 'here';
|
||||
return A();
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
getA(9).foo();
|
||||
// Ensure the default value is not inlined into the function.
|
||||
getA(8).foo(s: 'wow');
|
||||
}
|
||||
Reference in New Issue
Block a user