Version 2.17.0-23.0.dev
Merge commit 'ae593598f8a7708a40c37ab57b5f85d61b65224c' into 'dev'
diff --git a/pkg/front_end/benchmarks/patterns/test_lists.dart b/pkg/front_end/benchmarks/patterns/test_lists.dart
new file mode 100644
index 0000000..4a20f36
--- /dev/null
+++ b/pkg/front_end/benchmarks/patterns/test_lists.dart
@@ -0,0 +1,271 @@
+// Copyright (c) 2022, 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.
+
+import 'util.dart';
+
+typedef TestFunction<T> = List<T> Function(List<T>);
+
+class InputOutputData<T> {
+ final List<T> input;
+ final List<T> output;
+
+ const InputOutputData(this.input, this.output);
+}
+
+const Strategy simpleAddStrategy = Strategy('simple-add', '''
+Add entries to a list one at a time.''');
+
+const Strategy spreadStrategy = Strategy('spread', '''
+Create the list via spreads.''');
+
+const Strategy listGenerateGrowableStrategy =
+ Strategy('list-generate-growable', '''
+Create list via List.generate with growable = true.''');
+
+const Strategy listGenerateNotGrowableStrategy =
+ Strategy('list-generate-not-growable', '''
+Create list via List.generate with growable = false.''');
+
+const Strategy listFilledGrowableStrategy = Strategy('list-filled-growable', '''
+Create list via List.generate with growable = true.''');
+
+const Strategy listFilledNotGrowableStrategy =
+ Strategy('list-filled-not-growable', '''
+Create list via List.generate with growable = false.''');
+
+const Strategy listFilledAlternativeGrowableStrategy =
+ Strategy('list-filled-alt-growable', '''
+Create list via List.generate with growable = true in an alternative way.''');
+
+const Strategy listFilledAlternativeNotGrowableStrategy =
+ Strategy('list-filled-alt-not-growable', '''
+Create list via List.generate with growable = false in an alternative way.''');
+
+const Scenario emptyScenario = Scenario('empty', '''
+The input and output is empty.''');
+const Scenario oneEntryScenario = Scenario('one', '''
+The input is one entry.''');
+const Scenario severalEntriesScenario = Scenario('several', '''
+The input has several entries.''');
+
+Map<Scenario, InputOutputData<String>> scenarios = {
+ emptyScenario: const InputOutputData([], []),
+ oneEntryScenario: const InputOutputData(["a"], ["<", "a", ">"]),
+ severalEntriesScenario: const InputOutputData(
+ [
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ ],
+ [
+ "<",
+ "a",
+ ", ",
+ "b",
+ ", ",
+ "c",
+ ", ",
+ "d",
+ ", ",
+ "e",
+ ", ",
+ "f",
+ ", ",
+ "g",
+ ">",
+ ],
+ ),
+};
+
+class Test<T> {
+ final int size;
+ final Map<Strategy, TestFunction<T>> strategies;
+
+ const Test(this.size, this.strategies);
+
+ void _test(Registry registry, SeriesKey key, int runs, int iterations,
+ List<T> input, List<T> expectedResult, TestFunction<T> testFunction) {
+ for (int run = 0; run < runs; run++) {
+ Stopwatch sw = new Stopwatch();
+ for (int i = 0; i < iterations; i++) {
+ sw.start();
+ List<T> actualOutput = testFunction(input);
+ sw.stop();
+ checkEquals(actualOutput, expectedResult);
+ }
+ registry.registerData(key, size, sw.elapsedMicroseconds);
+ }
+ }
+
+ void checkEquals(List<T> a, List<T> b) {
+ if (a.length != b.length) throw "length for $a vs $b";
+ for (int i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) throw "index $i for $a vs $b";
+ }
+ }
+
+ void performTest(
+ {required Registry registry,
+ required int runs,
+ required int iterations,
+ required Map<Scenario, InputOutputData<T>> scenarios}) {
+ for (MapEntry<Scenario, InputOutputData<T>> scenario in scenarios.entries) {
+ List<T> scenarioInput = scenario.value.input;
+ List<T> scenarioExpectedOutput = scenario.value.output;
+ for (MapEntry<Strategy, TestFunction<T>> entry in strategies.entries) {
+ _test(registry, new SeriesKey(entry.key, scenario.key), runs,
+ iterations, scenarioInput, scenarioExpectedOutput, entry.value);
+ }
+ }
+ }
+}
+
+List<Test<String>> tests = [
+ // "size" isn't used here...
+ Test<String>(-1, {
+ simpleAddStrategy: simplyAdd,
+ listGenerateGrowableStrategy: listGenerateGrowable,
+ listGenerateNotGrowableStrategy: listGenerateNotGrowable,
+ listFilledGrowableStrategy: listFilledGrowable,
+ listFilledNotGrowableStrategy: listFilledNotGrowable,
+ listFilledAlternativeGrowableStrategy: listFilledAlternativeGrowable,
+ listFilledAlternativeNotGrowableStrategy: listFilledAlternativeNotGrowable,
+ spreadStrategy: spread,
+ }),
+];
+
+List<String> simplyAdd(List<String> input) {
+ if (input.isEmpty) return [];
+ List<String> result = [];
+ result.add("<");
+ result.add(input[0]);
+ for (int i = 1; i < input.length; i++) {
+ result.add(", ");
+ result.add(input[i]);
+ }
+ result.add(">");
+ return result;
+}
+
+List<String> listGenerateGrowable(List<String> input) {
+ if (input.isEmpty) return [];
+ int size = input.length * 2 + 1;
+ return List<String>.generate(size, (index) {
+ if (index == 0) return "<";
+ if (index == size - 1) return ">";
+ if (index.isEven) return ", ";
+ return input[index >> 1];
+ }, growable: true);
+}
+
+List<String> listGenerateNotGrowable(List<String> input) {
+ if (input.isEmpty) return [];
+ int size = input.length * 2 + 1;
+ return List<String>.generate(size, (index) {
+ if (index == 0) return "<";
+ if (index == size - 1) return ">";
+ if (index.isEven) return ", ";
+ return input[index >> 1];
+ }, growable: false);
+}
+
+List<String> listFilledAlternativeGrowable(List<String> input) {
+ if (input.isEmpty) return [];
+ int size = input.length * 2 + 1;
+ List<String> result = List<String>.filled(size, ", ", growable: true);
+ for (int i = 0; i < input.length; i++) {
+ result[(i << 1) + 1] = input[i];
+ }
+ result[0] = "<";
+ result[result.length - 1] = ">";
+
+ return result;
+}
+
+List<String> listFilledAlternativeNotGrowable(List<String> input) {
+ if (input.isEmpty) return [];
+ int size = input.length * 2 + 1;
+ List<String> result = List<String>.filled(size, ", ", growable: false);
+ for (int i = 0; i < input.length; i++) {
+ result[(i << 1) + 1] = input[i];
+ }
+ result[0] = "<";
+ result[result.length - 1] = ">";
+
+ return result;
+}
+
+List<String> listFilledGrowable(List<String> input) {
+ if (input.isEmpty) return [];
+ int size = input.length * 2 + 1;
+ List<String> result = List<String>.filled(size, "", growable: true);
+ int j = 0;
+ result[j++] = "<";
+ result[j++] = input[0];
+ for (int i = 1; i < input.length; i++) {
+ result[j++] = ", ";
+ result[j++] = input[i];
+ }
+ result[j++] = ">";
+
+ return result;
+}
+
+List<String> listFilledNotGrowable(List<String> input) {
+ if (input.isEmpty) return [];
+ int size = input.length * 2 + 1;
+ List<String> result = List<String>.filled(size, "", growable: false);
+ int j = 0;
+ result[j++] = "<";
+ result[j++] = input[0];
+ for (int i = 1; i < input.length; i++) {
+ result[j++] = ", ";
+ result[j++] = input[i];
+ }
+ result[j++] = ">";
+
+ return result;
+}
+
+List<String> spread(List<String> input) {
+ return [
+ if (input.isNotEmpty) ...[
+ '<',
+ input.first,
+ for (String s in input.skip(1)) ...[', ', s],
+ '>',
+ ]
+ ];
+}
+
+void main() {
+ // Dry run
+ for (Test test in tests) {
+ test.performTest(
+ registry: new Registry(),
+ runs: 5,
+ iterations: 10,
+ scenarios: scenarios);
+ }
+ // Actual test
+ Registry registry = new Registry();
+ for (Test test in tests) {
+ test.performTest(
+ registry: registry, runs: 10, iterations: 100000, scenarios: scenarios);
+ }
+ SeriesSet seriesSet = registry.generateSeriesSet();
+ print('== Raw data ==');
+ for (Scenario scenario in scenarios.keys) {
+ print(seriesSet.getFullSpreadByScenario(scenario));
+ }
+ print('== Reduced averages ==');
+ SeriesSet reducedSeriesSet = seriesSet.filter((list) => removeMax(list, 3));
+ for (Scenario scenario in scenarios.keys) {
+ print(reducedSeriesSet.getAveragedSpreadByScenario(scenario));
+ }
+}
diff --git a/pkg/kernel/lib/util/graph.dart b/pkg/kernel/lib/util/graph.dart
index 105b170b..eef4b6b 100644
--- a/pkg/kernel/lib/util/graph.dart
+++ b/pkg/kernel/lib/util/graph.dart
@@ -208,3 +208,37 @@
/// That is, `layers[i]` contain the list of vertices with index `i`.
final List<List<T>> layers = [];
}
+
+/// Find vertices in [graph] that either is in [vertices], has a neighbor in
+/// [vertices], has a neighbor of a neighbor (etc) in [vertices].
+Set<T> calculateTransitiveDependenciesOf<T>(Graph<T> graph, Set<T> vertices) {
+ // Compute direct dependencies for each vertex (the reverse of the
+ // edges returned by `graph.neighborsOf`).
+ Map<T, Set<T>> directDependencies = <T, Set<T>>{};
+ List<T> workList = [];
+ {
+ for (T vertex in graph.vertices) {
+ if (vertices.contains(vertex)) workList.add(vertex);
+ for (T neighbor in graph.neighborsOf(vertex)) {
+ (directDependencies[neighbor] ??= new Set<T>()).add(vertex);
+ if (vertices.contains(neighbor)) workList.add(vertex);
+ }
+ }
+ }
+
+ // Collect and remove all dependencies.
+ Set<T> left = new Set<T>.from(graph.vertices);
+ Set<T> transitive = {};
+ while (workList.isNotEmpty) {
+ T removed = workList.removeLast();
+ if (left.remove(removed)) {
+ Set<T>? s = directDependencies[removed];
+ if (s != null) {
+ // [s] is null for leaves.
+ workList.addAll(s);
+ }
+ transitive.add(removed);
+ }
+ }
+ return transitive;
+}
diff --git a/pkg/kernel/test/graph_test.dart b/pkg/kernel/test/graph_test.dart
index 83827be..cbcb0d1 100644
--- a/pkg/kernel/test/graph_test.dart
+++ b/pkg/kernel/test/graph_test.dart
@@ -556,4 +556,103 @@
[F]
]
]);
+
+ testTransitiveDependencies(graphData: {
+ A: [B],
+ B: [A],
+ }, of: {
+ A
+ }, expected: {
+ A,
+ B,
+ });
+
+ testTransitiveDependencies(graphData: {}, of: {
+ A,
+ B,
+ C,
+ D,
+ E,
+ F,
+ }, expected: {});
+
+ {
+ String MAIN = "MAIN";
+ String DARTFFI = "DARTFFI";
+ testTransitiveDependencies(graphData: {
+ MAIN: [DARTFFI],
+ }, of: {
+ DARTFFI
+ }, expected: {
+ MAIN,
+ });
+ }
+
+ testTransitiveDependencies(graphData: {
+ A: [B],
+ B: [C],
+ C: [B, D],
+ D: [C],
+ E: [A],
+ F: [B],
+ }, of: {
+ A
+ }, expected: {
+ A,
+ E
+ });
+
+ {
+ String MAIN = "MAIN";
+ String TARGET = "TARGET";
+
+ testTransitiveDependencies(graphData: {
+ MAIN: [A, E],
+ A: [B],
+ B: [A, C],
+ C: [TARGET],
+ D: [],
+ E: [D],
+ }, of: {
+ TARGET,
+ }, expected: {
+ C,
+ B,
+ A,
+ MAIN,
+ });
+
+ testTransitiveDependencies(graphData: {
+ MAIN: [A, E],
+ A: [B],
+ B: [A, C],
+ C: [],
+ D: [],
+ E: [D],
+ }, of: {
+ TARGET,
+ }, expected: {});
+
+ testTransitiveDependencies(graphData: {
+ MAIN: [A, C, E, TARGET],
+ A: [B],
+ B: [A, C],
+ C: [],
+ D: [],
+ E: [D],
+ }, of: {
+ TARGET,
+ }, expected: {
+ MAIN,
+ });
+ }
+}
+
+void testTransitiveDependencies(
+ {required Set<String> of,
+ required Set<String> expected,
+ required Map<String, List<String>> graphData}) {
+ Graph<String> graph = new TestGraph(graphData);
+ Set<String> actual = calculateTransitiveDependenciesOf(graph, of);
+ Expect.setEquals(expected, actual);
}
diff --git a/pkg/vm/lib/target/vm.dart b/pkg/vm/lib/target/vm.dart
index da77a9e..b1a60d8 100644
--- a/pkg/vm/lib/target/vm.dart
+++ b/pkg/vm/lib/target/vm.dart
@@ -18,7 +18,8 @@
import '../transformations/call_site_annotator.dart' as callSiteAnnotator;
import '../transformations/lowering.dart' as lowering
show transformLibraries, transformProcedure;
-import '../transformations/ffi/common.dart' as ffiHelper show importsFfi;
+import '../transformations/ffi/common.dart' as ffiHelper
+ show calculateTransitiveImportsOfDartFfiIfUsed;
import '../transformations/ffi/definitions.dart' as transformFfiDefinitions
show transformLibraries;
import '../transformations/ffi/native.dart' as transformFfiNative
@@ -154,7 +155,9 @@
this, coreTypes, hierarchy, libraries, referenceFromIndex);
logger?.call("Transformed mixin applications");
- if (!ffiHelper.importsFfi(component, libraries)) {
+ List<Library>? transitiveImportingDartFfi = ffiHelper
+ .calculateTransitiveImportsOfDartFfiIfUsed(component, libraries);
+ if (transitiveImportingDartFfi == null) {
logger?.call("Skipped ffi transformation");
} else {
// Transform @FfiNative(..) functions into FFI native call functions.
@@ -163,21 +166,19 @@
// Transform arguments that extend NativeFieldWrapperClass1 to Pointer if
// the native function expects Pointer (to avoid Handle overhead).
transformFfiNative.transformLibraries(component, coreTypes, hierarchy,
- libraries, diagnosticReporter, referenceFromIndex);
+ transitiveImportingDartFfi, diagnosticReporter, referenceFromIndex);
logger?.call("Transformed ffi natives");
- // TODO(jensj/dacoharkes): We can probably limit the transformations to
- // libraries that transitivley depend on dart:ffi.
transformFfiDefinitions.transformLibraries(
component,
coreTypes,
hierarchy,
- libraries,
+ transitiveImportingDartFfi,
diagnosticReporter,
referenceFromIndex,
changedStructureNotifier);
transformFfiUseSites.transformLibraries(component, coreTypes, hierarchy,
- libraries, diagnosticReporter, referenceFromIndex);
+ transitiveImportingDartFfi, diagnosticReporter, referenceFromIndex);
logger?.call("Transformed ffi annotations");
}
diff --git a/pkg/vm/lib/transformations/ffi/common.dart b/pkg/vm/lib/transformations/ffi/common.dart
index 4446159..c23bddb 100644
--- a/pkg/vm/lib/transformations/ffi/common.dart
+++ b/pkg/vm/lib/transformations/ffi/common.dart
@@ -16,6 +16,7 @@
import 'package:kernel/type_algebra.dart' show Substitution;
import 'package:kernel/type_environment.dart'
show TypeEnvironment, SubtypeCheckMode;
+import 'package:kernel/util/graph.dart' as kernelGraph;
import 'abi.dart';
import 'native_type_cfe.dart';
@@ -980,18 +981,61 @@
}
}
+/// Returns all libraries including the ones from component except for platform
+/// libraries that are only in component.
+Set<Library> _getAllRelevantLibraries(
+ Component component, List<Library> libraries) {
+ Set<Library> allLibs = {};
+ allLibs.addAll(libraries);
+ for (Library lib in component.libraries) {
+ // Skip real dart: libraries. dart:core imports dart:ffi, but that doesn't
+ // mean we have to transform anything.
+ if (lib.importUri.scheme == "dart" && !lib.isSynthetic) continue;
+ allLibs.add(lib);
+ }
+ return allLibs;
+}
+
/// Checks if any library depends on dart:ffi.
-bool importsFfi(Component component, List<Library> libraries) {
- Set<Library> allLibs = {...component.libraries, ...libraries};
+Library? importsFfi(Component component, List<Library> libraries) {
final Uri dartFfiUri = Uri.parse("dart:ffi");
+ Set<Library> allLibs = _getAllRelevantLibraries(component, libraries);
for (Library lib in allLibs) {
for (LibraryDependency dependency in lib.dependencies) {
- if (dependency.targetLibrary.importUri == dartFfiUri) {
- return true;
+ Library targetLibrary = dependency.targetLibrary;
+ if (targetLibrary.importUri == dartFfiUri) {
+ return targetLibrary;
}
}
}
- return false;
+ return null;
+}
+
+/// Calculates the libraries in [libraries] that transitively imports dart:ffi.
+///
+/// Returns null if dart:ffi is not imported.
+List<Library>? calculateTransitiveImportsOfDartFfiIfUsed(
+ Component component, List<Library> libraries) {
+ Set<Library> allLibs = _getAllRelevantLibraries(component, libraries);
+
+ final Uri dartFfiUri = Uri.parse("dart:ffi");
+ Library? dartFfi;
+ canFind:
+ for (Library lib in allLibs) {
+ for (LibraryDependency dependency in lib.dependencies) {
+ Library targetLibrary = dependency.targetLibrary;
+ if (targetLibrary.importUri == dartFfiUri) {
+ dartFfi = targetLibrary;
+ break canFind;
+ }
+ }
+ }
+ if (dartFfi == null) return null;
+
+ kernelGraph.LibraryGraph graph = new kernelGraph.LibraryGraph(allLibs);
+ Set<Library> result =
+ kernelGraph.calculateTransitiveDependenciesOf(graph, {dartFfi});
+ return (result..retainAll(libraries)).toList();
}
extension on Map<Abi, Object?> {
diff --git a/tools/VERSION b/tools/VERSION
index bb88572..9973e4e 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 17
PATCH 0
-PRERELEASE 22
+PRERELEASE 23
PRERELEASE_PATCH 0
\ No newline at end of file