[dart2wasm] Filter out user-provided program constraints

When users provide program constraints, some of the library import
prefixes will be unreachable. Though the core partitioning algorithm in
dart2wasm will only work on imports sets of alive / reachable prefixes.

=> Make us filter out unreachable library prefixes before processing
   user-provided program constraints.
=> Add a check that those we filter out are still valid library prefixes
   from the Kernel AST.

Change-Id: Id55064ee03e682a04d5e9117e466ac9573e66a39
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/498901
Reviewed-by: Nate Biggs <natebiggs@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Martin Kustermann
2026-04-29 02:26:33 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 890f7e8aa9
commit ae33e9c12c
7 changed files with 463 additions and 7 deletions
+1
View File
@@ -10,6 +10,7 @@ analyzer:
exclude:
- test/ir_tests/*.dart
- test/deferred_loading/partition_tests/*.dart
linter:
rules:
+96 -3
View File
@@ -255,17 +255,22 @@ class _Algorithm {
final namedNodes = ProgramSplitBuilder();
final orderNodes = <OrderNode>[];
final filteredUserConstraints = _filterUserConstraints(
userConstraints,
allDeferredImportsIncludingRoot,
);
// If user provided constraints, initialize from them.
final existingNames = <String, NamedNode>{};
if (userConstraints != null) {
for (final named in userConstraints!.named) {
if (filteredUserConstraints != null) {
for (final named in filteredUserConstraints.named) {
if (named is ReferenceNode) {
final import = UriAndPrefix(named.uri, named.prefix).toString();
existingNames[import] = named;
}
namedNodes.namedNodes[named.name] = named;
}
for (final ordered in userConstraints!.ordered) {
for (final ordered in filteredUserConstraints.ordered) {
orderNodes.add(ordered);
}
}
@@ -298,6 +303,94 @@ class _Algorithm {
).build(allDeferredImportsIncludingRoot);
}
ConstraintData? _filterUserConstraints(
ConstraintData? userConstraints,
Set<LibraryDependency> allDeferredImportsIncludingRoot,
) {
if (userConstraints == null) return null;
/// The set of all prefixes (even unreachable ones).
final allPrefixNames = <String>{
for (final library in component.libraries)
for (final import in library.dependencies)
if (import.isDeferred) import.uriPrefix,
};
/// The set of reachable prefixes.
final validImportPrefixes = allDeferredImportsIncludingRoot
.map((d) => d.uriPrefix)
.toSet();
final newNamedNodes = <NamedNode, NamedNode>{};
// Find [ReferenceNode]s that exist.
for (final NamedNode node in userConstraints.named) {
if (node is ReferenceNode) {
final importPrefix = UriAndPrefix(node.uri, node.prefix).toString();
if (validImportPrefixes.contains(importPrefix)) {
newNamedNodes[node] = node;
continue;
}
if (!allPrefixNames.contains(importPrefix)) {
throw StateError('The library $importPrefix is not known.');
}
// The [importPrefix] is in the Kernel AST but unreachable. This can
// happen due to RTA+TFA leaving unreachable code behind. We therefore
// prune this node and simplify or remove depending constraint nodes
// below.
}
}
// Prune or remove [CombinerNode]s.
for (final NamedNode node in userConstraints.named) {
if (node is CombinerNode) {
final Set<ReferenceNode> remaining = node.nodes
.where(newNamedNodes.containsKey)
.toSet();
if (remaining.isEmpty) continue;
if (node.type == CombinerType.and &&
remaining.length < node.nodes.length) {
continue;
}
if (remaining.length == 1) {
newNamedNodes[node] = remaining.first;
} else {
newNamedNodes[node] = CombinerNode(node.name, node.type, remaining);
}
}
}
// Filter/prune [OrderNode]s.
final newOrderNodes = <OrderNode>[];
for (final node in userConstraints.ordered) {
if (node is RelativeOrderNode) {
final predecessor = newNamedNodes[node.predecessor];
final successor = newNamedNodes[node.successor];
if (predecessor != null && successor != null) {
newOrderNodes.add(
RelativeOrderNode(predecessor: predecessor, successor: successor),
);
}
continue;
}
if (node is FuseNode) {
final newNodes = <NamedNode>{};
for (final node in node.nodes) {
final newNode = newNamedNodes[node];
if (newNode == null) continue;
newNodes.add(newNode);
}
if (newNodes.length >= 2) {
newOrderNodes.add(FuseNode(newNodes));
}
continue;
}
throw StateError('Unknown order node $node.');
}
return ConstraintData(newNamedNodes.values.toList(), newOrderNodes);
}
void collectDependencies(Set<Reference> roots) {
for (final reference in roots) {
ensureReferenceDependencies(reference);
@@ -38,15 +38,36 @@ Future runDart2wasmTests() async {
.toList();
for (final mainFile in tests) {
final mainFilePath = mainFile.path;
final defaultSplitExpectationFile =
'${mainFilePath.substring(0, mainFilePath.length - '.dart'.length)}.default.txt';
final mainFileBasePath = mainFilePath.substring(
0,
mainFilePath.length - '.dart'.length,
);
// Test without constraints.
// Generate a partitioning without constraints under
// `<testname>.default.txt`.
await testPartitionExpectation(
mainFilePath,
null,
defaultSplitExpectationFile,
'$mainFileBasePath.default.txt',
);
// If the `<testname>.constraints.json` exists generate a partitioining with
// constraints under `<testname>.constraints.txt`.
final jsonConstraintsFile = File('$mainFileBasePath.constraints.json');
if (jsonConstraintsFile.existsSync()) {
final constraintsJsonString = jsonConstraintsFile
.readAsStringSync()
.replaceAll(
'\${SDK_ROOT}',
Directory.current.absolute.uri.toString(),
);
final constraints = Parser().read(constraintsJsonString);
await testPartitionExpectation(
mainFilePath,
constraints,
'$mainFileBasePath.constraints.txt',
);
}
}
}
@@ -0,0 +1,90 @@
[
{
"type": "reference",
"name": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#OrAlive",
"import": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#OrAlive"
},
{
"type": "reference",
"name": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#OrAlive_1",
"import": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#OrAlive_1"
},
{
"type": "reference",
"name": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#OrAlive_2",
"import": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#OrAlive_2"
},
{
"type": "reference",
"name": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#OrDead",
"import": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#OrDead"
},
{
"type": "reference",
"name": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#AndAlive",
"import": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#AndAlive"
},
{
"type": "reference",
"name": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#AndAlive_1",
"import": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#AndAlive_1"
},
{
"type": "reference",
"name": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#AndAlive_2",
"import": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#AndAlive_2"
},
{
"type": "reference",
"name": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#AndDead",
"import": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#AndDead"
},
{
"type": "reference",
"name": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#FuseAlive",
"import": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#FuseAlive"
},
{
"type": "reference",
"name": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#FuseAlive_1",
"import": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#FuseAlive_1"
},
{
"type": "reference",
"name": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#FuseAlive_2",
"import": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#FuseAlive_2"
},
{
"type": "reference",
"name": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#FuseDead",
"import": "${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#FuseDead"
},
{
"type": "or",
"name": "or_with_dead",
"nodes": [
"${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#OrAlive_1",
"${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#OrAlive_2",
"${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#OrDead"
]
},
{
"type": "and",
"name": "and_with_dead",
"nodes": [
"${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#AndAlive_1",
"${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#AndAlive_2",
"${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#AndDead"
]
},
{
"type": "fuse",
"nodes": [
"or_with_dead",
"and_with_dead",
"${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#FuseAlive_1",
"${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#FuseAlive_2",
"${SDK_ROOT}pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart#FuseDead"
]
}
]
@@ -0,0 +1,75 @@
Part 0
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: FuseAlive_1
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: FuseAlive_2
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive_1
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive_2
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::testOr
Constants
Part 1
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: AndAlive
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: AndAlive_1
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: AndAlive_2
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::testAnd
Constants
Part 2
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: FuseAlive
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: FuseAlive_1
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: FuseAlive_2
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive_1
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive_2
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::testFuse
Constants
Part 3
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive_1
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::orTest_alive_1
Constants
- StringConstant("orTest_alive_1")
Part 4
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive_2
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::orTest_alive_2
Constants
- StringConstant("orTest_alive_2")
Part 5
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: AndAlive_1
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::andTest_alive_1
Constants
- StringConstant("andTest_alive_1")
Part 6
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: AndAlive_2
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::andTest_alive_2
Constants
- StringConstant("andTest_alive_2")
Part 7
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: FuseAlive_1
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: FuseAlive_2
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive_1
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive_2
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::fuseTest_alive_1
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::fuseTest_alive_2
Constants
- StringConstant("fuseTest_alive_1")
- StringConstant("fuseTest_alive_2")
@@ -0,0 +1,102 @@
// 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.
// Or node
import '' deferred as OrAlive;
import '' deferred as OrAlive_1;
import '' deferred as OrAlive_2;
import '' deferred as OrDead;
// And node
import '' deferred as AndAlive;
import '' deferred as AndAlive_1;
import '' deferred as AndAlive_2;
import '' deferred as AndDead;
// Fuse node
import '' deferred as FuseAlive;
import '' deferred as FuseAlive_1;
import '' deferred as FuseAlive_2;
import '' deferred as FuseDead;
void main() async {
print('main');
switch (opaqueInt) {
case 1:
await OrAlive.loadLibrary();
await OrAlive.testOr();
break;
case 2:
await AndAlive.loadLibrary();
await AndAlive.testAnd();
break;
case 3:
await FuseAlive.loadLibrary();
await FuseAlive.testFuse();
break;
}
}
Future testOr() async {
switch (opaqueInt) {
case 1:
await OrAlive_1.loadLibrary();
OrAlive_1.orTest_alive_1();
break;
case 2:
await OrAlive_2.loadLibrary();
OrAlive_2.orTest_alive_2();
break;
default:
if (alwaysFalse) {
await OrDead.loadLibrary();
OrDead.orTest_dead();
}
break;
}
}
Future testAnd() async {
for (int i = 0; i < 3; ++i) {
if (i == 0) {
await AndAlive_1.loadLibrary();
AndAlive_1.andTest_alive_1();
} else if (i == 1) {
await AndAlive_2.loadLibrary();
AndAlive_2.andTest_alive_2();
} else {
if (!alwaysTrue) {
await AndDead.loadLibrary();
AndDead.andTest_dead();
}
}
}
}
Future testFuse() async {
await FuseAlive_1.loadLibrary();
FuseAlive_1.fuseTest_alive_1();
await FuseAlive_2.loadLibrary();
FuseAlive_2.fuseTest_alive_2();
if (alwaysFalse) {
await FuseDead.loadLibrary();
FuseDead.fuseTest_dead();
}
}
void orTest_alive_1() => print('orTest_alive_1');
void orTest_alive_2() => print('orTest_alive_2');
void orTest_dead() => print('orTest_dead');
void andTest_alive_1() => print('andTest_alive_1');
void andTest_alive_2() => print('andTest_alive_2');
void andTest_dead() => print('andTest_dead');
void fuseTest_alive_1() => print('fuseTest_alive_1');
void fuseTest_alive_2() => print('fuseTest_alive_2');
void fuseTest_dead() => print('fuseTest_dead');
int get opaqueInt => int.parse('1');
bool get alwaysTrue => true;
bool get alwaysFalse => false;
@@ -0,0 +1,74 @@
Part 0
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive_1
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive_2
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::testOr
Constants
Part 1
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: AndAlive
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: AndAlive_1
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: AndAlive_2
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::testAnd
Constants
Part 2
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: FuseAlive
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: FuseAlive_1
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: FuseAlive_2
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::testFuse
Constants
Part 3
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive_1
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::orTest_alive_1
Constants
- StringConstant("orTest_alive_1")
Part 4
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: OrAlive_2
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::orTest_alive_2
Constants
- StringConstant("orTest_alive_2")
Part 5
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: AndAlive_1
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::andTest_alive_1
Constants
- StringConstant("andTest_alive_1")
Part 6
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: AndAlive_2
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::andTest_alive_2
Constants
- StringConstant("andTest_alive_2")
Part 7
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: FuseAlive_1
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::fuseTest_alive_1
Constants
- StringConstant("fuseTest_alive_1")
Part 8
ImportSet
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart prefix: FuseAlive_2
References
- pkg/dart2wasm/test/deferred_loading/partition_tests/dead_constraint.dart::@methods::fuseTest_alive_2
Constants
- StringConstant("fuseTest_alive_2")