From ddfe681eb378b588f759dee8fa9593b186eac96d Mon Sep 17 00:00:00 2001 From: "sigurdm@google.com" Date: Mon, 19 May 2014 08:19:53 +0000 Subject: [PATCH] Avoid inlining constants that are used via a deferred import. In order to avoid values from a deferred library to leak into the source code, we create an intermediate constant and refer via that. This is also done for non-primitive constants, so that for a case like: main.dart: import "lib.dart" deferred as lib; class C { const C(); } main() { print(const C()); print(lib.C1); } lib.dart: import "main.dart" as main; const C1 = const main.C(); The main output file will not reveal that we are printing the same constant twice. This CL also changes the constant emitter to be split between a literal emitter and a reference emitter, instead of a initializer emitter and a reference emitter. R=johnniwinther@google.com Review URL: https://codereview.chromium.org//256453004 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@36286 260f80e4-7a28-3924-810f-c04153c831b5 --- .../compiler/implementation/constants.dart | 34 +++ .../compiler/implementation/dart2jslib.dart | 2 +- .../implementation/deferred_load.dart | 8 + .../js_backend/constant_emitter.dart | 240 ++++++++++-------- .../implementation/js_backend/namer.dart | 9 + .../js_emitter/code_emitter_task.dart | 46 ++-- .../compiler/implementation/ssa/builder.dart | 11 +- .../compiler/implementation/ssa/nodes.dart | 7 + .../ssa/value_range_analyzer.dart | 12 +- ...d_dont_inline_deferred_constants_test.dart | 143 +++++++++++ 10 files changed, 378 insertions(+), 134 deletions(-) create mode 100644 tests/compiler/dart2js/deferred_dont_inline_deferred_constants_test.dart diff --git a/sdk/lib/_internal/compiler/implementation/constants.dart b/sdk/lib/_internal/compiler/implementation/constants.dart index ab9a45bf722..8fe72129723 100644 --- a/sdk/lib/_internal/compiler/implementation/constants.dart +++ b/sdk/lib/_internal/compiler/implementation/constants.dart @@ -18,6 +18,7 @@ abstract class ConstantVisitor { R visitType(TypeConstant constant); R visitInterceptor(InterceptorConstant constant); R visitDummy(DummyConstant constant); + R visitDeferred(DeferredConstant constant); } abstract class Constant { @@ -652,3 +653,36 @@ class ConstructedConstant extends ObjectConstant { return sb.toString(); } } + +/// A reference to a constant in another output unit. +/// Used for referring to deferred constants. +class DeferredConstant extends Constant { + DeferredConstant(this.referenced, this.prefix); + + final Constant referenced; + final PrefixElement prefix; + + bool get isReference => true; + + bool operator ==(other) { + return other is DeferredConstant + && referenced == other.referenced + && prefix == other.prefix; + } + + get hashCode => (referenced.hashCode * 17 + prefix.hashCode) & 0x3fffffff; + + List getDependencies() => [referenced]; + + accept(ConstantVisitor visitor) => visitor.visitDeferred(this); + + DartType computeType(Compiler compiler) => referenced.computeType(compiler); + + ti.TypeMask computeMask(Compiler compiler) { + return referenced.computeMask(compiler); + } + + String toString() { + return 'DeferredConstant($referenced)'; + } +} diff --git a/sdk/lib/_internal/compiler/implementation/dart2jslib.dart b/sdk/lib/_internal/compiler/implementation/dart2jslib.dart index 58e9279f299..137bd266a7a 100644 --- a/sdk/lib/_internal/compiler/implementation/dart2jslib.dart +++ b/sdk/lib/_internal/compiler/implementation/dart2jslib.dart @@ -38,7 +38,7 @@ import 'resolution/resolution.dart'; import 'resolution/class_members.dart' show MembersCreator; import 'source_file.dart' show SourceFile; import 'js/js.dart' as js; -import 'deferred_load.dart' show DeferredLoadTask; +import 'deferred_load.dart' show DeferredLoadTask, OutputUnit; import 'mirrors_used.dart' show MirrorUsageAnalyzerTask; import 'dump_info.dart'; import 'tracer.dart' show Tracer; diff --git a/sdk/lib/_internal/compiler/implementation/deferred_load.dart b/sdk/lib/_internal/compiler/implementation/deferred_load.dart index b12ec4bb99e..ba055a7c598 100644 --- a/sdk/lib/_internal/compiler/implementation/deferred_load.dart +++ b/sdk/lib/_internal/compiler/implementation/deferred_load.dart @@ -11,6 +11,7 @@ import 'dart2jslib.dart' show Constant, ConstructedConstant, MessageKind, + DeferredConstant, StringConstant, invariant; @@ -189,6 +190,13 @@ class DeferredLoadTask extends CompilerTask { return outputUnitForElement(e1) == outputUnitForElement(e2); } + void registerConstantDeferredUse(DeferredConstant constant, + PrefixElement prefix) { + OutputUnit outputUnit = new OutputUnit(); + outputUnit.imports.add(prefix.deferredImport); + _constantToOutputUnit[constant] = outputUnit; + } + /// Mark that [import] is part of the [OutputputUnit] for [element]. /// /// [element] can be either a [Constant] or an [Element]. diff --git a/sdk/lib/_internal/compiler/implementation/js_backend/constant_emitter.dart b/sdk/lib/_internal/compiler/implementation/js_backend/constant_emitter.dart index d8e92454e8b..abf320760fd 100644 --- a/sdk/lib/_internal/compiler/implementation/js_backend/constant_emitter.dart +++ b/sdk/lib/_internal/compiler/implementation/js_backend/constant_emitter.dart @@ -4,14 +4,13 @@ part of js_backend; -class ConstantEmitter { +class ConstantEmitter { ConstantReferenceEmitter _referenceEmitter; - ConstantInitializerEmitter _initializerEmitter; + ConstantLiteralEmitter _literalEmitter; ConstantEmitter(Compiler compiler, Namer namer) { - _referenceEmitter = new ConstantReferenceEmitter(compiler, namer); - _initializerEmitter = new ConstantInitializerEmitter( - compiler, namer, _referenceEmitter); + _literalEmitter = new ConstantLiteralEmitter(compiler, namer, this); + _referenceEmitter = new ConstantReferenceEmitter(compiler, namer, this); } /** @@ -23,19 +22,28 @@ class ConstantEmitter { return _referenceEmitter.generate(constant); } + /** + * Constructs a literal expression that evaluates to the constant. Uses a + * canonical name unless the constant can be emitted multiple times (as for + * numbers and strings). + */ + jsAst.Expression literal(Constant constant) { + return _literalEmitter.generate(constant); + } + /** * Constructs an expression like [reference], but the expression is valid * during isolate initialization. */ jsAst.Expression referenceInInitializationContext(Constant constant) { - return _referenceEmitter.generateInInitializationContext(constant); + return _referenceEmitter.generate(constant); } /** * Constructs an expression used to initialize a canonicalized constant. */ jsAst.Expression initializationExpression(Constant constant) { - return _initializerEmitter.generate(constant); + return _literalEmitter.generate(constant); } } @@ -47,13 +55,112 @@ class ConstantReferenceEmitter implements ConstantVisitor { final Compiler compiler; final Namer namer; - ConstantReferenceEmitter(this.compiler, this.namer); + final ConstantEmitter constantEmitter; + + ConstantReferenceEmitter(this.compiler, this.namer, this.constantEmitter); jsAst.Expression generate(Constant constant) { return _visit(constant); } - jsAst.Expression generateInInitializationContext(Constant constant) { + jsAst.Expression _visit(Constant constant) { + return constant.accept(this); + } + + jsAst.Expression emitCanonicalVersion(Constant constant) { + String name = namer.constantName(constant); + return new jsAst.PropertyAccess.field( + new jsAst.VariableUse(namer.globalObjectForConstant(constant)), name); + } + + jsAst.Expression literal(Constant constant) { + return constantEmitter.literal(constant); + } + + jsAst.Expression visitFunction(FunctionConstant constant) { + return namer.isolateStaticClosureAccess(constant.element); + } + + jsAst.Expression visitNull(NullConstant constant) { + return literal(constant); + } + + jsAst.Expression visitInt(IntConstant constant) { + return literal(constant); + } + + jsAst.Expression visitDouble(DoubleConstant constant) { + return literal(constant); + } + + jsAst.Expression visitTrue(TrueConstant constant) { + return literal(constant); + } + + jsAst.Expression visitFalse(FalseConstant constant) { + return literal(constant); + } + + /** + * Write the contents of the quoted string to a [CodeBuffer] in + * a form that is valid as JavaScript string literal content. + * The string is assumed quoted by double quote characters. + */ + jsAst.Expression visitString(StringConstant constant) { + // TODO(sra): If the string is long *and repeated* (and not on a hot path) + // then it should be assigned to a name. We don't have reference counts (or + // profile information) here, so this is the wrong place. + return literal(constant); + } + + jsAst.Expression visitList(ListConstant constant) { + return emitCanonicalVersion(constant); + } + + jsAst.Expression visitMap(MapConstant constant) { + return emitCanonicalVersion(constant); + } + + jsAst.Expression visitType(TypeConstant constant) { + return emitCanonicalVersion(constant); + } + + jsAst.Expression visitConstructed(ConstructedConstant constant) { + return emitCanonicalVersion(constant); + } + + jsAst.Expression visitInterceptor(InterceptorConstant constant) { + return emitCanonicalVersion(constant); + } + + jsAst.Expression visitDummy(DummyConstant constant) { + return literal(constant); + } + + jsAst.Expression visitDeferred(DeferredConstant constant) { + return emitCanonicalVersion(constant); + } +} + +/** + * Visitor for generating JavaScript expressions that litterally represent + * [Constant]s. These can be used for inlining constants or in initializers. + * Do not use directly, use methods from [ConstantEmitter]. + */ +class ConstantLiteralEmitter implements ConstantVisitor { + + // Matches blank lines, comment lines and trailing comments that can't be part + // of a string. + static final RegExp COMMENT_RE = + new RegExp(r'''^ *(//.*)?\n| *//[^''"\n]*$''' , multiLine: true); + + final Compiler compiler; + final Namer namer; + final ConstantEmitter constantEmitter; + + ConstantLiteralEmitter(this.compiler, this.namer, this.constantEmitter); + + jsAst.Expression generate(Constant constant) { return _visit(constant); } @@ -62,7 +169,9 @@ class ConstantReferenceEmitter implements ConstantVisitor { } jsAst.Expression visitFunction(FunctionConstant constant) { - return namer.isolateStaticClosureAccess(constant.element); + compiler.internalError(NO_LOCATION_SPANNABLE, + "The function constant does not need specific JS code."); + return null; } jsAst.Expression visitNull(NullConstant constant) { @@ -110,104 +219,11 @@ class ConstantReferenceEmitter implements ConstantVisitor { * The string is assumed quoted by double quote characters. */ jsAst.Expression visitString(StringConstant constant) { - // TODO(sra): If the string is long *and repeated* (and not on a hot path) - // then it should be assigned to a name. We don't have reference counts (or - // profile information) here, so this is the wrong place. StringBuffer sb = new StringBuffer(); writeJsonEscapedCharsOn(constant.value.slowToString(), sb); return new jsAst.LiteralString('"$sb"'); } - jsAst.Expression emitCanonicalVersion(Constant constant) { - String name = namer.constantName(constant); - return new jsAst.PropertyAccess.field( - new jsAst.VariableUse(namer.globalObjectForConstant(constant)), name); - } - - jsAst.Expression visitList(ListConstant constant) { - return emitCanonicalVersion(constant); - } - - jsAst.Expression visitMap(MapConstant constant) { - return emitCanonicalVersion(constant); - } - - jsAst.Expression visitType(TypeConstant constant) { - return emitCanonicalVersion(constant); - } - - jsAst.Expression visitConstructed(ConstructedConstant constant) { - return emitCanonicalVersion(constant); - } - - jsAst.Expression visitInterceptor(InterceptorConstant constant) { - return emitCanonicalVersion(constant); - } - - jsAst.Expression visitDummy(DummyConstant constant) { - return new jsAst.LiteralNumber('0'); - } -} - -/** - * Visitor for generating JavaScript expressions to initialize [Constant]s. - * Do not use directly; use methods from [ConstantEmitter]. - */ -class ConstantInitializerEmitter implements ConstantVisitor { - final Compiler compiler; - final Namer namer; - final ConstantReferenceEmitter referenceEmitter; - - // Matches blank lines, comment lines and trailing comments that can't be part - // of a string. - static final RegExp COMMENT_RE = - new RegExp(r'''^ *(//.*)?\n| *//[^''"\n]*$''' , multiLine: true); - - ConstantInitializerEmitter(this.compiler, this.namer, this.referenceEmitter); - - jsAst.Expression generate(Constant constant) { - return _visit(constant); - } - - jsAst.Expression _visit(Constant constant) { - return constant.accept(this); - } - - jsAst.Expression _reference(Constant constant) { - return referenceEmitter.generateInInitializationContext(constant); - } - - jsAst.Expression visitFunction(FunctionConstant constant) { - compiler.internalError(NO_LOCATION_SPANNABLE, - "The function constant does not need specific JS code."); - return null; - } - - jsAst.Expression visitNull(NullConstant constant) { - return _reference(constant); - } - - jsAst.Expression visitInt(IntConstant constant) { - return _reference(constant); - } - - jsAst.Expression visitDouble(DoubleConstant constant) { - return _reference(constant); - } - - jsAst.Expression visitTrue(TrueConstant constant) { - return _reference(constant); - } - - jsAst.Expression visitFalse(FalseConstant constant) { - return _reference(constant); - } - - jsAst.Expression visitString(StringConstant constant) { - // TODO(sra): Some larger strings are worth sharing. - return _reference(constant); - } - jsAst.Expression visitList(ListConstant constant) { jsAst.Expression value = new jsAst.Call( new jsAst.PropertyAccess.field( @@ -231,7 +247,7 @@ class ConstantInitializerEmitter implements ConstantVisitor { // Keys in literal maps must be emitted in place. jsAst.Literal keyExpression = _visit(key); jsAst.Expression valueExpression = - _reference(constant.values[i]); + constantEmitter.reference(constant.values[i]); properties.add(new jsAst.Property(keyExpression, valueExpression)); } return new jsAst.ObjectInitializer(properties); @@ -241,9 +257,9 @@ class ConstantInitializerEmitter implements ConstantVisitor { List data = []; for (int i = 0; i < constant.keys.entries.length; i++) { jsAst.Expression keyExpression = - _reference(constant.keys.entries[i]); + constantEmitter.reference(constant.keys.entries[i]); jsAst.Expression valueExpression = - _reference(constant.values[i]); + constantEmitter.reference(constant.values[i]); data.add(keyExpression); data.add(valueExpression); } @@ -266,10 +282,10 @@ class ConstantInitializerEmitter implements ConstantVisitor { } else if (field.name == MapConstant.JS_OBJECT_NAME) { arguments.add(jsMap()); } else if (field.name == MapConstant.KEYS_NAME) { - arguments.add(_reference(constant.keys)); + arguments.add(constantEmitter.reference(constant.keys)); } else if (field.name == MapConstant.PROTO_VALUE) { assert(constant.protoValue != null); - arguments.add(_reference(constant.protoValue)); + arguments.add(constantEmitter.reference(constant.protoValue)); } else if (field.name == MapConstant.JS_DATA_NAME) { arguments.add(jsGeneralMap()); } else { @@ -316,7 +332,7 @@ class ConstantInitializerEmitter implements ConstantVisitor { } jsAst.Expression visitDummy(DummyConstant constant) { - return _reference(constant); + return new jsAst.LiteralNumber('0'); } jsAst.Expression visitConstructed(ConstructedConstant constant) { @@ -340,7 +356,7 @@ class ConstantInitializerEmitter implements ConstantVisitor { List _array(List values) { List valueList = []; for (int i = 0; i < values.length; i++) { - valueList.add(_reference(values[i])); + valueList.add(constantEmitter.reference(values[i])); } return valueList; } @@ -363,4 +379,8 @@ class ConstantInitializerEmitter implements ConstantVisitor { } return value; } + + jsAst.Expression visitDeferred(DeferredConstant constant) { + return constantEmitter.reference(constant.referenced); + } } diff --git a/sdk/lib/_internal/compiler/implementation/js_backend/namer.dart b/sdk/lib/_internal/compiler/implementation/js_backend/namer.dart index a652c7c8efb..db55415182e 100644 --- a/sdk/lib/_internal/compiler/implementation/js_backend/namer.dart +++ b/sdk/lib/_internal/compiler/implementation/js_backend/namer.dart @@ -1163,6 +1163,10 @@ class ConstantNamingVisitor implements ConstantVisitor { visitDummy(DummyConstant constant) { add('dummy_receiver'); } + + visitDeferred(DeferredConstant constant) { + addRoot('Deferred'); + } } /** @@ -1245,6 +1249,11 @@ class ConstantCanonicalHasher implements ConstantVisitor { 'DummyReceiverConstant should never be named and never be subconstant'); } + visitDeferred(DeferredConstant constant) { + int hash = constant.prefix.hashCode; + return _combine(hash, constant.referenced.accept(this)); + } + int _hashString(int hash, String s) { int length = s.length; hash = _combine(hash, length); diff --git a/sdk/lib/_internal/compiler/implementation/js_emitter/code_emitter_task.dart b/sdk/lib/_internal/compiler/implementation/js_emitter/code_emitter_task.dart index 5daff8ea438..0060900d8ce 100644 --- a/sdk/lib/_internal/compiler/implementation/js_emitter/code_emitter_task.dart +++ b/sdk/lib/_internal/compiler/implementation/js_emitter/code_emitter_task.dart @@ -33,6 +33,8 @@ class CodeEmitterTask extends CompilerTask { final Set neededClasses = new Set(); final Map> outputClassLists = new Map>(); + final Map> outputConstantLists = + new Map>(); final List nativeClasses = []; final Map mangledFieldNames = {}; final Map mangledGlobalFieldNames = {}; @@ -890,26 +892,9 @@ class CodeEmitterTask extends CompilerTask { } void emitCompileTimeConstants(CodeBuffer buffer, OutputUnit outputUnit) { - JavaScriptConstantCompiler handler = backend.constants; - List constants = handler.getConstantsForEmission( - compareConstants); - Set outputUnitConstants = null; - // TODO(sigurdm): We shouldn't run through all constants for every - // outputUnit. + List constants = outputConstantLists[outputUnit]; + if (constants == null) return; for (Constant constant in constants) { - if (isConstantInlinedOrAlreadyEmitted(constant)) continue; - OutputUnit constantUnit = - compiler.deferredLoadTask.outputUnitForConstant(constant); - if (constantUnit != outputUnit && constantUnit != null) continue; - if (outputUnit != compiler.deferredLoadTask.mainOutputUnit - && constantUnit == null) { - // The back-end introduces some constants, like "InterceptorConstant" or - // some list constants. They are emitted in the main output-unit, and - // ignored otherwise. - // TODO(sigurdm): We should track those constants. - continue; - } - String name = namer.constantName(constant); if (constant.isList) emitMakeConstantListIfNotEmitted(buffer); jsAst.Expression init = js('#.# = #', @@ -1093,6 +1078,28 @@ class CodeEmitterTask extends CompilerTask { addComment('END invoke [main].', buffer); } + /** + * Compute all the constants that must be emitted. + */ + void computeNeededConstants() { + JavaScriptConstantCompiler handler = backend.constants; + List constants = handler.getConstantsForEmission( + compareConstants); + for (Constant constant in constants) { + if (isConstantInlinedOrAlreadyEmitted(constant)) continue; + OutputUnit constantUnit = + compiler.deferredLoadTask.outputUnitForConstant(constant); + if (constantUnit == null) { + // The back-end introduces some constants, like "InterceptorConstant" or + // some list constants. They are emitted in the main output-unit. + // TODO(sigurdm): We should track those constants. + constantUnit = compiler.deferredLoadTask.mainOutputUnit; + } + outputConstantLists.putIfAbsent(constantUnit, () => new List()) + .add(constant); + } + } + /** * Compute all the classes that must be emitted. */ @@ -1464,6 +1471,7 @@ class CodeEmitterTask extends CompilerTask { // which may need getInterceptor (and one-shot interceptor) methods, so // we have to make sure that [emitGetInterceptorMethods] and // [emitOneShotInterceptors] have been called. + computeNeededConstants(); emitCompileTimeConstants(mainBuffer, mainOutputUnit); // Write a javascript mapping from Deferred import load ids (derrived from diff --git a/sdk/lib/_internal/compiler/implementation/ssa/builder.dart b/sdk/lib/_internal/compiler/implementation/ssa/builder.dart index 786fc5fa56c..fcef11087ef 100644 --- a/sdk/lib/_internal/compiler/implementation/ssa/builder.dart +++ b/sdk/lib/_internal/compiler/implementation/ssa/builder.dart @@ -2996,7 +2996,16 @@ class SsaBuilder extends ResolvedVisitor { value = backend.constants.getConstantForVariable(element); } if (value != null) { - HConstant instruction = graph.addConstant(value, compiler); + HConstant instruction; + // Constants that are referred via a deferred prefix should be referred + // by reference. + PrefixElement prefix = compiler.deferredLoadTask + .deferredPrefixElement(send, elements); + if (prefix != null) { + instruction = graph.addDeferredConstant(value, prefix, compiler); + } else { + instruction = graph.addConstant(value, compiler); + } stack.add(instruction); // The inferrer may have found a better type than the constant // handler in the case of lists, because the constant handler diff --git a/sdk/lib/_internal/compiler/implementation/ssa/nodes.dart b/sdk/lib/_internal/compiler/implementation/ssa/nodes.dart index c0487207e7d..a8107f4cf08 100644 --- a/sdk/lib/_internal/compiler/implementation/ssa/nodes.dart +++ b/sdk/lib/_internal/compiler/implementation/ssa/nodes.dart @@ -184,6 +184,13 @@ class HGraph { return result; } + HConstant addDeferredConstant(Constant constant, PrefixElement prefix, + Compiler compiler) { + Constant wrapper = new DeferredConstant(constant, prefix); + compiler.deferredLoadTask.registerConstantDeferredUse(wrapper, prefix); + return addConstant(wrapper, compiler); + } + HConstant addConstantInt(int i, Compiler compiler) { return addConstant(compiler.backend.constantSystem.createInt(i), compiler); } diff --git a/sdk/lib/_internal/compiler/implementation/ssa/value_range_analyzer.dart b/sdk/lib/_internal/compiler/implementation/ssa/value_range_analyzer.dart index f8dcf42c09f..f5800293054 100644 --- a/sdk/lib/_internal/compiler/implementation/ssa/value_range_analyzer.dart +++ b/sdk/lib/_internal/compiler/implementation/ssa/value_range_analyzer.dart @@ -670,9 +670,15 @@ class SsaValueRangeAnalyzer extends HBaseVisitor implements OptimizationPhase { return range; } - Range visitConstant(HConstant constant) { - if (!constant.isInteger(compiler)) return info.newUnboundRange(); - NumConstant constantNum = constant.constant; + Range visitConstant(HConstant hConstant) { + if (!hConstant.isInteger(compiler)) return info.newUnboundRange(); + Constant constant = hConstant.constant; + NumConstant constantNum; + if (constant is DeferredConstant) { + constantNum = constant.referenced; + } else { + constantNum = constant; + } if (constantNum.isMinusZero) constantNum = new IntConstant(0); Value value = info.newIntValue(constantNum.value); return info.newNormalizedRange(value, value); diff --git a/tests/compiler/dart2js/deferred_dont_inline_deferred_constants_test.dart b/tests/compiler/dart2js/deferred_dont_inline_deferred_constants_test.dart new file mode 100644 index 00000000000..487b733549d --- /dev/null +++ b/tests/compiler/dart2js/deferred_dont_inline_deferred_constants_test.dart @@ -0,0 +1,143 @@ +// 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:expect/expect.dart'; +import "package:async_helper/async_helper.dart"; +import 'memory_source_file_helper.dart'; +import "dart:async"; + +import '../../../sdk/lib/_internal/compiler/implementation/dart2jslib.dart' + as dart2js; + +class MemoryOutputSink extends EventSink { + StringBuffer mem = new StringBuffer(); + void add(String event) { + mem.write(event); + } + void addError(String event, [StackTrace stackTrace]) { + Expect.isTrue(false); + } + void close() {} +} + +void main() { + Uri script = currentDirectory.resolveUri(Platform.script); + Uri libraryRoot = script.resolve('../../../sdk/'); + Uri packageRoot = script.resolve('./packages/'); + + var provider = new MemorySourceFileProvider(MEMORY_SOURCE_FILES); + var handler = new FormattingDiagnosticHandler(provider); + + Map outputs = new Map(); + + MemoryOutputSink outputSaver(name, extension) { + if (name == '') { + name = 'main'; + } + return outputs.putIfAbsent("$name.$extension", () { + return new MemoryOutputSink(); + }); + } + + Compiler compiler = new Compiler(provider.readStringFromUri, + outputSaver, + handler.diagnosticHandler, + libraryRoot, + packageRoot, + [], + {}); + asyncTest(() => compiler.run(Uri.parse('memory:main.dart')).then((_) { + String mainOutput = outputs['main.js'].mem.toString(); + String lib1Output = outputs['out_lib1.part.js'].mem.toString(); + String lib2Output = outputs['out_lib2.part.js'].mem.toString(); + String lib12Output = outputs['out_lib1_lib2.part.js'].mem.toString(); + // Test that the deferred constants are not inlined into the main file. + RegExp re1 = new RegExp(r"= .string1"); + RegExp re2 = new RegExp(r"= .string2"); + RegExp re3 = new RegExp(r"= 1010"); + Expect.isTrue(re1.hasMatch(lib1Output)); + Expect.isTrue(re2.hasMatch(lib1Output)); + Expect.isTrue(re3.hasMatch(lib1Output)); + Expect.isFalse(re1.hasMatch(mainOutput)); + Expect.isFalse(re2.hasMatch(mainOutput)); + Expect.isFalse(re3.hasMatch(mainOutput)); + // Test that the non-deferred constant is inlined. + Expect.isTrue(new RegExp(r"print\(.string3.\)").hasMatch(mainOutput)); + Expect.isFalse(new RegExp(r"= .string3").hasMatch(mainOutput)); + Expect.isTrue(new RegExp(r"print\(.string4.\)").hasMatch(mainOutput)); + + // 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. + Expect.isTrue(new RegExp(r"C.C_1 =").hasMatch(mainOutput)); + Expect.isFalse(new RegExp(r"= C.C_1").hasMatch(mainOutput)); + + Expect.isTrue(new RegExp(r"= C.C_1").hasMatch(lib1Output)); + Expect.isTrue(new RegExp(r"= C.C_1").hasMatch(lib2Output)); + + // C(2) is shared between lib1 and lib2, each of them has their own + // reference to it. + Expect.isFalse(new RegExp(r"= C.C_2").hasMatch(mainOutput)); + + Expect.isTrue(new RegExp(r"= C.C_2").hasMatch(lib1Output)); + Expect.isTrue(new RegExp(r"= C.C_2").hasMatch(lib2Output)); + Expect.isTrue(new RegExp(r"C.C_2 =").hasMatch(lib12Output)); + + // "string4" is shared between lib1 and lib2, but it can be inlined. + Expect.isTrue(new RegExp(r"= .string4").hasMatch(lib1Output)); + Expect.isTrue(new RegExp(r"= .string4").hasMatch(lib2Output)); + Expect.isFalse(new RegExp(r"= .string4").hasMatch(lib12Output)); + })); +} + +// Make sure that deferred constants are not inlined into the main hunk. +const Map MEMORY_SOURCE_FILES = const {"main.dart": """ +import "dart:async"; + +import 'lib1.dart' deferred as lib1; +import 'lib2.dart' deferred as lib2; + +const c = "string3"; + +class C { + final p; + const C(this.p); +} + +void main() { + lib1.loadLibrary().then((_) { + lib2.loadLibrary().then((_) { + print(lib1.C1); + print(lib1.C2); + print(lib1.C.C3); + print(c); + print(lib1.C4); + print(lib2.C4); + print(lib1.C5); + print(lib2.C5); + print(lib1.C6); + print(lib2.C6); + print("string4"); + print(const C(1)); + }); + }); +} +""", "lib1.dart": """ +import "main.dart" as main; +const C1 = "string1"; +const C2 = 1010; +class C { + static const C3 = "string2"; +} +const C4 = "string4"; +const C5 = const main.C(1); +const C6 = const main.C(2); +""", "lib2.dart": """ +import "main.dart" as main; +const C4 = "string4"; +const C5 = const main.C(1); +const C6 = const main.C(2); +"""};