Skip to content

Commit

Permalink
Use hasOnlyNonDeferredImportPathsToConstant to add deferred constants
Browse files Browse the repository at this point in the history
Change-Id: Ifd8b733c526fe4e7ae837a7d17c682e91717feb5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/98013
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
  • Loading branch information
johnniwinther authored and commit-bot@chromium.org committed Mar 28, 2019
1 parent 32a5fa0 commit 10abacb
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 160 deletions.
15 changes: 15 additions & 0 deletions pkg/compiler/lib/src/deferred_load.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1439,6 +1439,21 @@ class OutputUnitData {
return outputUnitTo._imports.containsAll(outputUnitFrom._imports);
}

/// Returns `true` if constant [to] is reachable from element [from] without
/// crossing a deferred import.
///
/// For example, if we have two deferred libraries `A` and `B` that both
/// import a library `C`, then even though elements from `A` and `C` end up in
/// different output units, there is a non-deferred path between `A` and `C`.
bool hasOnlyNonDeferredImportPathsToConstant(
MemberEntity from, ConstantValue to) {
OutputUnit outputUnitFrom = outputUnitForMember(from);
OutputUnit outputUnitTo = outputUnitForConstant(to);
if (outputUnitTo == mainOutputUnit) return true;
if (outputUnitFrom == mainOutputUnit) return false;
return outputUnitTo._imports.containsAll(outputUnitFrom._imports);
}

/// Registers that a constant is used in the same deferred output unit as
/// [field].
void registerConstantDeferredUse(
Expand Down
4 changes: 2 additions & 2 deletions pkg/compiler/lib/src/ssa/builder_kernel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1527,8 +1527,8 @@ class KernelSsaGraphBuilder extends ir.Visitor
@override
void visitConstantExpression(ir.ConstantExpression node) {
ConstantValue value = _elementMap.getConstantValue(node);
ir.LibraryDependency import = getDeferredImport(node);
if (import != null) {
if (!closedWorld.outputUnitData
.hasOnlyNonDeferredImportPathsToConstant(targetElement, value)) {
stack.add(graph.addDeferredConstant(
value,
closedWorld.outputUnitData.outputUnitForConstant(value),
Expand Down
93 changes: 93 additions & 0 deletions tests/compiler/dart2js/deferred/constant_emission_test_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) 2014, 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.

// Test that the additional runtime type support is output to the right
// Files when using deferred loading.

import 'package:compiler/compiler_new.dart';
import 'package:compiler/src/commandline_options.dart';
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/constants/values.dart';
import 'package:compiler/src/deferred_load.dart';
import 'package:compiler/src/elements/entities.dart';
import 'package:compiler/src/js_emitter/model.dart';
import 'package:compiler/src/util/util.dart';
import 'package:expect/expect.dart';
import '../helpers/memory_compiler.dart';
import '../helpers/output_collector.dart';
import '../helpers/program_lookup.dart';

class OutputUnitDescriptor {
final String uri;
final String member;
final String name;

const OutputUnitDescriptor(this.uri, this.member, this.name);
}

run(Map<String, String> sourceFiles, List<OutputUnitDescriptor> outputUnits,
Map<String, Set<String>> expectedOutputUnits,
{bool useCFEConstants: false}) async {
OutputCollector collector = new OutputCollector();
CompilationResult result = await runCompiler(
memorySourceFiles: sourceFiles,
outputProvider: collector,
options: useCFEConstants
? ['${Flags.enableLanguageExperiments}=constant-update-2018']
: ['${Flags.enableLanguageExperiments}=no-constant-update-2018']);
Compiler compiler = result.compiler;
ProgramLookup lookup = new ProgramLookup(compiler);
var closedWorld = compiler.backendClosedWorldForTesting;
var elementEnvironment = closedWorld.elementEnvironment;

LibraryEntity lookupLibrary(name) {
return elementEnvironment.lookupLibrary(Uri.parse(name));
}

OutputUnit Function(MemberEntity) outputUnitForMember =
closedWorld.outputUnitData.outputUnitForMember;

Map<String, Fragment> fragments = {};
fragments['main'] = lookup.program.mainFragment;

for (OutputUnitDescriptor descriptor in outputUnits) {
LibraryEntity library = lookupLibrary(descriptor.uri);
MemberEntity member =
elementEnvironment.lookupLibraryMember(library, descriptor.member);
OutputUnit outputUnit = outputUnitForMember(member);
fragments[descriptor.name] = lookup.getFragment(outputUnit);
}

Map<String, Set<String>> actualOutputUnits = {};

bool errorsFound = false;

void processFragment(String fragmentName, Fragment fragment) {
for (Constant constant in fragment.constants) {
String text = constant.value.toStructuredText();
Set<String> expectedConstantUnit = expectedOutputUnits[text];
if (expectedConstantUnit == null) {
if (constant.value is DeferredGlobalConstantValue) {
print('ERROR: No expectancy for $constant found in $fragmentName');
errorsFound = true;
}
} else {
(actualOutputUnits[text] ??= <String>{}).add(fragmentName);
}
}
}

fragments.forEach(processFragment);

expectedOutputUnits.forEach((String constant, Set<String> expectedSet) {
Set<String> actualSet = actualOutputUnits[constant] ?? const <String>{};
if (!equalSets(expectedSet, actualSet)) {
print("ERROR: Constant $constant found in $actualSet, expected "
"$expectedSet");
errorsFound = true;
}
});

Expect.isFalse(errorsFound, "Errors found.");
}
95 changes: 95 additions & 0 deletions tests/compiler/dart2js/deferred/deferred_constant3_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) 2014, 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.

// Test that the additional runtime type support is output to the right
// Files when using deferred loading.

import 'package:async_helper/async_helper.dart';
import 'constant_emission_test_helper.dart';

void main() {
runTest({bool useCFEConstants: false}) async {
Map<String, Set<String>> expectedOutputUnits = {
'ConstructedConstant(C(x=IntConstant(1)))': {'main'},
'DeferredGlobalConstant(ConstructedConstant(C(x=IntConstant(1))))':
// With CFE constants, the references are inlined, so the constant
// only occurs in main.
useCFEConstants ? {} : {'lib2'},
'ConstructedConstant(C(x=IntConstant(2)))': {'lib1'},
'DeferredGlobalConstant(ConstructedConstant(C(x=IntConstant(2))))': {
'lib1'
},
'ConstructedConstant(C(x=IntConstant(3)))': {'lib1'},
'ConstructedConstant(C(x=IntConstant(4)))': {'lib2'},
'DeferredGlobalConstant(ConstructedConstant(C(x=IntConstant(4))))': {
'lib2'
},
'ConstructedConstant(C(x=IntConstant(5)))': {'lib2'},
};
await run(
MEMORY_SOURCE_FILES,
const [
OutputUnitDescriptor('memory:lib1.dart', 'm1', 'lib1'),
OutputUnitDescriptor('memory:lib2.dart', 'm2', 'lib2'),
],
expectedOutputUnits,
useCFEConstants: useCFEConstants);
}

asyncTest(() async {
print('--test from kernel------------------------------------------------');
await runTest();
print('--test from kernel with CFE constants-----------------------------');
await runTest(useCFEConstants: true);
});
}

// Make sure that deferred constants are not inlined into the main hunk.
const Map<String, String> MEMORY_SOURCE_FILES = const {
"main.dart": r"""
import 'c.dart';
import 'lib1.dart' deferred as l1;
const c1 = const C(1);
main() async {
print(c1.x);
await l1.loadLibrary();
l1.m1();
print(l1.c2);
}
""",
"lib1.dart": """
import 'c.dart';
import 'lib2.dart' deferred as l2;
const c2 = const C(2);
const c3 = const C(3);
m1() async {
print(c2);
print(c3);
await l2.loadLibrary();
l2.m2();
print(l2.c3);
print(l2.c4);
}
""",
"lib2.dart": """
import 'c.dart';
const c3 = const C(1);
const c4 = const C(4);
const c5 = const C(5);
m2() async {
print(c3);
print(c4);
print(c5);
}
""",
"c.dart": """
class C { const C(this.x); final x; }
""",
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,113 +6,46 @@
// Files when using deferred loading.

import 'package:async_helper/async_helper.dart';
import 'package:compiler/compiler_new.dart';
import 'package:compiler/src/commandline_options.dart';
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/constants/values.dart';
import 'package:compiler/src/deferred_load.dart';
import 'package:compiler/src/elements/entities.dart';
import 'package:compiler/src/js_emitter/model.dart';
import 'package:expect/expect.dart';
import '../helpers/memory_compiler.dart';
import '../helpers/output_collector.dart';
import '../helpers/program_lookup.dart';
import 'constant_emission_test_helper.dart';

void main() {
runTest({bool useCFEConstants: false}) async {
OutputCollector collector = new OutputCollector();
CompilationResult result = await runCompiler(
memorySourceFiles: MEMORY_SOURCE_FILES,
outputProvider: collector,
options: useCFEConstants
? ['${Flags.enableLanguageExperiments}=constant-update-2018']
: ['${Flags.enableLanguageExperiments}=no-constant-update-2018']);
Compiler compiler = result.compiler;
ProgramLookup lookup = new ProgramLookup(compiler);
var closedWorld = compiler.backendClosedWorldForTesting;
var elementEnvironment = closedWorld.elementEnvironment;

LibraryEntity lookupLibrary(name) {
return elementEnvironment.lookupLibrary(Uri.parse(name));
}

OutputUnit Function(MemberEntity) outputUnitForMember =
closedWorld.outputUnitData.outputUnitForMember;

LibraryEntity lib1 = lookupLibrary("memory:lib1.dart");
MemberEntity foo1 = elementEnvironment.lookupLibraryMember(lib1, "foo");
OutputUnit ou_lib1 = outputUnitForMember(foo1);

LibraryEntity lib2 = lookupLibrary("memory:lib2.dart");
MemberEntity foo2 = elementEnvironment.lookupLibraryMember(lib2, "foo");
OutputUnit ou_lib2 = outputUnitForMember(foo2);

LibraryEntity mainApp = elementEnvironment.mainLibrary;
MemberEntity fooMain =
elementEnvironment.lookupLibraryMember(mainApp, "foo");
OutputUnit ou_lib1_lib2 = outputUnitForMember(fooMain);

Map<String, Set<String>> expectedOutputUnits = {
// Test that the deferred constants are not inlined into the main file.
'IntConstant(1010)': {'lib1'},
'StringConstant("string1")': {'lib1'},
'StringConstant("string2")': {'lib1'},
'DeferredGlobalConstant(IntConstant(1010))': {'lib1'},
'DeferredGlobalConstant(StringConstant("string1"))': {'lib1'},
'DeferredGlobalConstant(StringConstant("string2"))': {'lib1'},
// "string4" is shared between lib1 and lib2, but it can be inlined.
'StringConstant("string4")':
'DeferredGlobalConstant(StringConstant("string4"))':
// TODO(johnniwinther): Should we inline CFE constants within deferred
// library boundaries?
useCFEConstants ? {'lib12'} : {'lib1', 'lib2'},
// C(1) is shared between main, lib1 and lib2. Test that lib1 and lib2
// each has a reference to it. It is defined in the main output file.
'ConstructedConstant(C(p=IntConstant(1)))':
// With CFE constants, the references are inlined, so the constant only
// occurs in main.
useCFEConstants ? {'main'} : {'main', 'lib1', 'lib2'},
'ConstructedConstant(C(p=IntConstant(1)))': {'main'},
'DeferredGlobalConstant(ConstructedConstant(C(p=IntConstant(1))))':
// With CFE constants, the references are inlined, so the constant
// only occurs in main.
useCFEConstants ? {} : {'lib1', 'lib2'},
// C(2) is shared between lib1 and lib2, each of them has their own
// reference to it.
'ConstructedConstant(C(p=IntConstant(2)))':
'ConstructedConstant(C(p=IntConstant(2)))': {'lib12'},
'DeferredGlobalConstant(ConstructedConstant(C(p=IntConstant(2))))':
// With CFE constants, the references are inlined, so the constant
// occurs in lib12.
useCFEConstants ? {'lib12'} : {'lib1', 'lib2', 'lib12'},
useCFEConstants ? {'lib12'} : {'lib1', 'lib2'},
// Test that the non-deferred constant is inlined.
'ConstructedConstant(C(p=IntConstant(5)))': {'main'},
};

Map<String, Set<String>> actualOutputUnits = {};

void processFragment(Fragment fragment, String fragmentName) {
for (Constant constant in fragment.constants) {
String text;
if (constant.value is DeferredGlobalConstantValue) {
DeferredGlobalConstantValue deferred = constant.value;
text = deferred.referenced.toStructuredText();
} else {
text = constant.value.toStructuredText();
}
Set<String> expectedConstantUnit = expectedOutputUnits[text];
if (expectedConstantUnit == null) {
if (constant.value is DeferredGlobalConstantValue) {
print('No expectancy for $constant found in $fragmentName');
}
} else {
(actualOutputUnits[text] ??= <String>{}).add(fragmentName);
}
}
}

processFragment(lookup.program.mainFragment, 'main');
processFragment(lookup.getFragment(ou_lib1), 'lib1');
processFragment(lookup.getFragment(ou_lib2), 'lib2');
processFragment(lookup.getFragment(ou_lib1_lib2), 'lib12');

expectedOutputUnits.forEach((String constant, Set<String> expectedSet) {
Set<String> actualSet = actualOutputUnits[constant] ?? const <String>{};
Expect.setEquals(
expectedSet,
actualSet,
"Constant $constant found in $actualSet, expected "
"$expectedSet");
});
await run(
MEMORY_SOURCE_FILES,
const [
OutputUnitDescriptor('memory:lib1.dart', 'foo', 'lib1'),
OutputUnitDescriptor('memory:lib2.dart', 'foo', 'lib2'),
OutputUnitDescriptor('memory:main.dart', 'foo', 'lib12')
],
expectedOutputUnits,
useCFEConstants: useCFEConstants);
}

asyncTest(() async {
Expand Down
Loading

0 comments on commit 10abacb

Please sign in to comment.