Recognize and inline effectively constant fields
Change-Id: I243ee80ebc45d9cdab2f7c944cf5eceb26185b2c
Reviewed-on: https://dart-review.googlesource.com/c/93436
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/lib/src/constant_system_dart.dart b/pkg/compiler/lib/src/constant_system_dart.dart
index 95a75db..c610490 100644
--- a/pkg/compiler/lib/src/constant_system_dart.dart
+++ b/pkg/compiler/lib/src/constant_system_dart.dart
@@ -443,6 +443,8 @@
final round = const UnfoldedUnaryOperation('round');
final abs = const UnfoldedUnaryOperation('abs');
+ // TODO(johnniwinther): Delete this (embed parts into JavaScript constant
+ // system where needed).
const DartConstantSystem();
@override
@@ -475,7 +477,7 @@
@override
MapConstantValue createMap(CommonElements commonElements, InterfaceType type,
List<ConstantValue> keys, List<ConstantValue> values) {
- return new MapConstantValue(type, keys, values);
+ throw new UnsupportedError('DartConstantSystem.createMap');
}
@override
diff --git a/pkg/compiler/lib/src/constants/values.dart b/pkg/compiler/lib/src/constants/values.dart
index 5ad14d9..4f2a8a42 100644
--- a/pkg/compiler/lib/src/constants/values.dart
+++ b/pkg/compiler/lib/src/constants/values.dart
@@ -582,7 +582,7 @@
ConstantValueKind get kind => ConstantValueKind.SET;
}
-class MapConstantValue extends ObjectConstantValue {
+abstract class MapConstantValue extends ObjectConstantValue {
final List<ConstantValue> keys;
final List<ConstantValue> values;
final int hashCode;
diff --git a/pkg/compiler/lib/src/js_backend/annotations.dart b/pkg/compiler/lib/src/js_backend/annotations.dart
index 5290143..6c6f32e 100644
--- a/pkg/compiler/lib/src/js_backend/annotations.dart
+++ b/pkg/compiler/lib/src/js_backend/annotations.dart
@@ -10,6 +10,7 @@
import '../diagnostics/messages.dart';
import '../elements/entities.dart';
import '../kernel/dart2js_target.dart';
+import '../options.dart';
import '../serialization/serialization.dart';
import '../util/enumset.dart';
@@ -95,6 +96,7 @@
}
Set<PragmaAnnotation> processMemberAnnotations(
+ CompilerOptions options,
DiagnosticReporter reporter,
KCommonElements commonElements,
KElementEnvironment elementEnvironment,
@@ -107,7 +109,8 @@
}
LibraryEntity library = element.library;
- bool platformAnnotationsAllowed = library.canonicalUri.scheme == 'dart' ||
+ bool platformAnnotationsAllowed = options.testMode ||
+ library.canonicalUri.scheme == 'dart' ||
maybeEnableNative(library.canonicalUri);
for (ConstantValue constantValue
diff --git a/pkg/compiler/lib/src/js_backend/field_analysis.dart b/pkg/compiler/lib/src/js_backend/field_analysis.dart
index 554a32b..8489a25 100644
--- a/pkg/compiler/lib/src/js_backend/field_analysis.dart
+++ b/pkg/compiler/lib/src/js_backend/field_analysis.dart
@@ -182,9 +182,12 @@
// --csp and --fast-startup have different constraints to the generated code.
final Map<FieldEntity, ConstantValue> _fixedInitializers;
+ final Map<FieldEntity, ConstantValue> _effectivelyConstantFields;
+
final Set<FieldEntity> _elidedFields;
- JFieldAnalysis._(this._fixedInitializers, this._elidedFields);
+ JFieldAnalysis._(this._fixedInitializers, this._effectivelyConstantFields,
+ this._elidedFields);
/// Deserializes a [JFieldAnalysis] object from [source].
factory JFieldAnalysis.readFromDataSource(
@@ -192,15 +195,19 @@
source.begin(tag);
Map<FieldEntity, ConstantValue> fixedInitializers =
source.readMemberMap(source.readConstant);
+ Map<FieldEntity, ConstantValue> effectivelyConstantFields =
+ source.readMemberMap(source.readConstant);
Set<FieldEntity> elidedFields = source.readMembers<FieldEntity>().toSet();
source.end(tag);
- return new JFieldAnalysis._(fixedInitializers, elidedFields);
+ return new JFieldAnalysis._(
+ fixedInitializers, effectivelyConstantFields, elidedFields);
}
/// Serializes this [JFieldAnalysis] to [sink].
void writeToDataSink(DataSink sink) {
sink.begin(tag);
sink.writeMemberMap(_fixedInitializers, sink.writeConstant);
+ sink.writeMemberMap(_effectivelyConstantFields, sink.writeConstant);
sink.writeMembers(_elidedFields);
sink.end(tag);
}
@@ -208,39 +215,64 @@
factory JFieldAnalysis.from(
KClosedWorld closedWorld, JsToFrontendMap map, CompilerOptions options) {
Map<FieldEntity, ConstantValue> fixedInitializers = {};
+ Map<FieldEntity, ConstantValue> effectivelyConstantFields = {};
+ Set<FieldEntity> elidedFields = new Set();
+
+ bool canBeElided(FieldEntity field) {
+ return !closedWorld.annotationsData.hasNoElision(field) &&
+ !closedWorld.nativeData.isNativeMember(field);
+ }
+
closedWorld.fieldAnalysis._fixedInitializers
.forEach((KField kField, AllocatorData data) {
- // TODO(johnniwinther): Use liveness of constructors and elided optional
- // parameters to recognize more constant initializers.
- if (data.initialValue != null && data.initializers.isEmpty) {
- ConstantValue value = data.initialValue;
- if (value.isNull || value.isInt || value.isBool || value.isString) {
- // TODO(johnniwinther,sra): Support non-primitive constants in
- // allocators when it does cause allocators to deoptimized because
- // of deferred loading.
+ JField jField = map.toBackendMember(kField);
+ if (jField == null) {
+ return;
+ }
- JField jField = map.toBackendMember(kField);
- if (jField != null) {
- fixedInitializers[jField] = map.toBackendConstant(value);
+ // TODO(johnniwinther): Should elided static fields be removed from the
+ // J model? Static setters might still assign to them.
+
+ MemberUsage memberUsage = closedWorld.liveMemberUsage[kField];
+ if (!memberUsage.hasRead) {
+ if (canBeElided(kField)) {
+ elidedFields.add(jField);
+ }
+ } else {
+ // TODO(johnniwinther): Use liveness of constructors and elided optional
+ // parameters to recognize more constant initializers.
+ if (data.initialValue != null && data.initializers.isEmpty) {
+ ConstantValue value = map.toBackendConstant(data.initialValue);
+ assert(value != null);
+ if (!memberUsage.hasWrite && canBeElided(kField)) {
+ elidedFields.add(jField);
+ effectivelyConstantFields[jField] = value;
+ } else if (value.isNull ||
+ value.isInt ||
+ value.isBool ||
+ value.isString) {
+ // TODO(johnniwinther,sra): Support non-primitive constants in
+ // allocators when it does cause allocators to deoptimized because
+ // of deferred loading.
+ fixedInitializers[jField] = value;
}
}
}
});
- Set<FieldEntity> elidedFields = new Set();
+ // TODO(johnniwinther): Recognize effectively constant top level/static
+ // fields.
closedWorld.liveMemberUsage
.forEach((MemberEntity member, MemberUsage memberUsage) {
- // TODO(johnniwinther): Should elided static fields be removed from the
- // J model? Static setters might still assign to them.
- if (member.isField &&
- !memberUsage.hasRead &&
- !closedWorld.annotationsData.hasNoElision(member) &&
- !closedWorld.nativeData.isNativeMember(member)) {
- elidedFields.add(map.toBackendMember(member));
+ if (member.isField && !member.isInstanceMember) {
+ if (!memberUsage.hasRead && canBeElided(member)) {
+ elidedFields.add(map.toBackendMember(member));
+ }
}
});
- return new JFieldAnalysis._(fixedInitializers, elidedFields);
+ return new JFieldAnalysis._(
+ fixedInitializers, effectivelyConstantFields, elidedFields);
}
// TODO(sra): Add way to let injected fields be initialized to a constant in
@@ -254,18 +286,23 @@
/// Return the constant for a field initialized in allocator. Returns `null`
/// for fields not initialized in allocator.
ConstantValue initializerValue(JField field) {
+ assert(isInitializedInAllocator(field));
return _fixedInitializers[field];
}
/// Returns `true` if [field] can be elided from the output.
///
/// This happens if a field is written to but never read.
- // TODO(johnniwinther): Include fields that are effectively final.
bool isElided(JField field) => _elidedFields.contains(field);
/// Returns `true` if [field] is effectively constant and therefore only
/// holds its [initializerValue].
- // TODO(johnniwinther): Recognize fields that are initialized to a constant
- // but never written to.
- bool isEffectivelyConstant(JField field) => false;
+ bool isEffectivelyConstant(JField field) =>
+ _effectivelyConstantFields.containsKey(field);
+
+ /// Returns the [ConstantValue] for the effectively constant [field].
+ ConstantValue getConstantValue(JField field) {
+ assert(isEffectivelyConstant(field));
+ return _effectivelyConstantFields[field];
+ }
}
diff --git a/pkg/compiler/lib/src/js_backend/namer.dart b/pkg/compiler/lib/src/js_backend/namer.dart
index 9f3dbda..087ede4 100644
--- a/pkg/compiler/lib/src/js_backend/namer.dart
+++ b/pkg/compiler/lib/src/js_backend/namer.dart
@@ -574,7 +574,7 @@
}
/// Used to disambiguate names for constants in [constantName].
- final NamingScope constantScope = new NamingScope();
+ final NamingScope _constantScope = new NamingScope();
/// Used to store scopes for instances of [PrivatelyNamedJsEntity]
final Map<Entity, NamingScope> _privateNamingScopes = {};
@@ -583,8 +583,8 @@
final Map<LibraryEntity, String> libraryLongNames = HashMap();
- final Map<ConstantValue, jsAst.Name> constantNames = HashMap();
- final Map<ConstantValue, String> constantLongNames = {};
+ final Map<ConstantValue, jsAst.Name> _constantNames = HashMap();
+ final Map<ConstantValue, String> _constantLongNames = {};
ConstantCanonicalHasher _constantHasher;
/// Maps private names to a library that may use that name without prefixing
@@ -718,25 +718,25 @@
// function constants since the function-implementation itself serves as
// constant and can be accessed directly.
assert(!constant.isFunction);
- jsAst.Name result = constantNames[constant];
+ jsAst.Name result = _constantNames[constant];
if (result == null) {
String longName = constantLongName(constant);
- result = getFreshName(constantScope, longName);
- constantNames[constant] = result;
+ result = getFreshName(_constantScope, longName);
+ _constantNames[constant] = result;
}
return _newReference(result);
}
/// Proposed name for [constant].
String constantLongName(ConstantValue constant) {
- String longName = constantLongNames[constant];
+ String longName = _constantLongNames[constant];
if (longName == null) {
_constantHasher ??=
new ConstantCanonicalHasher(rtiEncoder, _codegenWorldBuilder);
longName = new ConstantNamingVisitor(
rtiEncoder, _codegenWorldBuilder, _constantHasher)
.getName(constant);
- constantLongNames[constant] = longName;
+ _constantLongNames[constant] = longName;
}
return longName;
}
diff --git a/pkg/compiler/lib/src/js_emitter/model.dart b/pkg/compiler/lib/src/js_emitter/model.dart
index 9fb47bc..6f7b580 100644
--- a/pkg/compiler/lib/src/js_emitter/model.dart
+++ b/pkg/compiler/lib/src/js_emitter/model.dart
@@ -384,6 +384,8 @@
final ConstantValue initializerInAllocator;
+ final ConstantValue constantValue;
+
final bool isElided;
// TODO(floitsch): support renamed fields.
@@ -395,6 +397,7 @@
this.setterFlags,
this.needsCheckedSetter,
this.initializerInAllocator,
+ this.constantValue,
this.isElided);
bool get needsGetter => getterFlags != 0;
diff --git a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
index 77089be..371dbfe 100644
--- a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
+++ b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
@@ -1071,10 +1071,14 @@
}
}
- ConstantValue initializerInAllocator = null;
+ ConstantValue initializerInAllocator;
if (_allocatorAnalysis.isInitializedInAllocator(field)) {
initializerInAllocator = _allocatorAnalysis.initializerValue(field);
}
+ ConstantValue constantValue;
+ if (_allocatorAnalysis.isEffectivelyConstant(field)) {
+ constantValue = _allocatorAnalysis.getConstantValue(field);
+ }
fields.add(new Field(
field,
@@ -1084,6 +1088,7 @@
setterFlags,
needsCheckedSetter,
initializerInAllocator,
+ constantValue,
_closedWorld.fieldAnalysis.isElided(field)));
}
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index 965e232..e47c77a 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -1078,17 +1078,33 @@
Method generateGetter(Field field) {
assert(field.needsGetter);
- String template;
- if (field.needsInterceptedGetterOnReceiver) {
- template = "function(receiver) { return receiver[#]; }";
- } else if (field.needsInterceptedGetterOnThis) {
- template = "function(receiver) { return this[#]; }";
+ js.Expression code;
+ if (field.isElided) {
+ ConstantValue constantValue = field.constantValue;
+ if (constantValue == null) {
+ assert(_closedWorld.abstractValueDomain is TrivialAbstractValueDomain);
+ // Since static types are not used in invocation/access of instance
+ // members in codegen, we see dynamic uses in codegen that are not
+ // present in resolution when using the trivial abstract value domain.
+ // This means that resolution can determine that a field is never read
+ // but codegen thinks it is read.
+ constantValue = new NullConstantValue();
+ }
+ code = js.js(
+ "function() { return #; }", generateConstantReference(constantValue));
} else {
- assert(!field.needsInterceptedGetter);
- template = "function() { return this[#]; }";
+ String template;
+ if (field.needsInterceptedGetterOnReceiver) {
+ template = "function(receiver) { return receiver[#]; }";
+ } else if (field.needsInterceptedGetterOnThis) {
+ template = "function(receiver) { return this[#]; }";
+ } else {
+ assert(!field.needsInterceptedGetter);
+ template = "function() { return this[#]; }";
+ }
+ js.Expression fieldName = js.quoteName(field.name);
+ code = js.js(template, fieldName);
}
- js.Expression fieldName = js.quoteName(field.name);
- js.Expression code = js.js(template, fieldName);
js.Name getterName = namer.deriveGetterName(field.accessorName);
return new StubMethod(getterName, code);
}
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart
index 5304aac..6497bf4 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/model_emitter.dart
@@ -32,10 +32,12 @@
import '../../../compiler_new.dart';
import '../../common.dart';
import '../../compiler.dart' show Compiler;
-import '../../constants/values.dart' show ConstantValue, FunctionConstantValue;
+import '../../constants/values.dart'
+ show ConstantValue, FunctionConstantValue, NullConstantValue;
import '../../common_elements.dart' show CommonElements;
import '../../elements/entities.dart';
import '../../hash/sha1.dart' show Hasher;
+import '../../inferrer/trivial.dart';
import '../../io/code_output.dart';
import '../../io/location_provider.dart' show LocationCollector;
import '../../io/source_map_builder.dart' show SourceMapBuilder;
diff --git a/pkg/compiler/lib/src/js_model/js_world_builder.dart b/pkg/compiler/lib/src/js_model/js_world_builder.dart
index 6467d7c..89f07bb 100644
--- a/pkg/compiler/lib/src/js_model/js_world_builder.dart
+++ b/pkg/compiler/lib/src/js_model/js_world_builder.dart
@@ -16,8 +16,9 @@
import '../inferrer/abstract_value_domain.dart';
import '../ir/closure.dart';
import '../js_backend/annotations.dart';
-import '../js_backend/field_analysis.dart';
import '../js_backend/backend_usage.dart';
+import '../js_backend/constant_system_javascript.dart';
+import '../js_backend/field_analysis.dart';
import '../js_backend/interceptor_data.dart';
import '../js_backend/native_data.dart';
import '../js_backend/no_such_method_registry.dart';
@@ -859,16 +860,19 @@
return new SetConstantValue(type, values);
}
- ConstantValue visitMap(MapConstantValue constant, _) {
- var type = typeConverter.visit(constant.type, toBackendEntity);
- List<ConstantValue> keys = _handleValues(constant.keys);
+ ConstantValue visitMap(covariant JavaScriptMapConstant constant, _) {
+ DartType type = typeConverter.visit(constant.type, toBackendEntity);
+ ListConstantValue keys = constant.keyList.accept(this, null);
List<ConstantValue> values = _handleValues(constant.values);
+ ConstantValue protoValue = constant.protoValue?.accept(this, null);
if (identical(keys, constant.keys) &&
identical(values, constant.values) &&
- type == constant.type) {
+ type == constant.type &&
+ protoValue == constant.protoValue) {
return constant;
}
- return new MapConstantValue(type, keys, values);
+ return new JavaScriptMapConstant(
+ type, keys, values, protoValue, constant.onlyStringKeys);
}
ConstantValue visitConstructed(ConstructedConstantValue constant, _) {
diff --git a/pkg/compiler/lib/src/kernel/kernel_strategy.dart b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
index b8a9090..2986cb7 100644
--- a/pkg/compiler/lib/src/kernel/kernel_strategy.dart
+++ b/pkg/compiler/lib/src/kernel/kernel_strategy.dart
@@ -227,6 +227,7 @@
return _compilerTask.measure(() {
_nativeMemberResolver.resolveNativeMember(element);
Set<PragmaAnnotation> annotations = processMemberAnnotations(
+ _elementMap.options,
_elementMap.reporter,
_elementMap.commonElements,
_elementMap.elementEnvironment,
diff --git a/pkg/compiler/lib/src/serialization/abstract_sink.dart b/pkg/compiler/lib/src/serialization/abstract_sink.dart
index 9f58478..07db268 100644
--- a/pkg/compiler/lib/src/serialization/abstract_sink.dart
+++ b/pkg/compiler/lib/src/serialization/abstract_sink.dart
@@ -401,10 +401,12 @@
writeConstants(constant.values);
break;
case ConstantValueKind.MAP:
- MapConstantValue constant = value;
+ JavaScriptMapConstant constant = value;
writeDartType(constant.type);
- writeConstants(constant.keys);
+ writeConstant(constant.keyList);
writeConstants(constant.values);
+ writeConstantOrNull(constant.protoValue);
+ writeBool(constant.onlyStringKeys);
break;
case ConstantValueKind.CONSTRUCTED:
ConstructedConstantValue constant = value;
diff --git a/pkg/compiler/lib/src/serialization/abstract_source.dart b/pkg/compiler/lib/src/serialization/abstract_source.dart
index e67a868..4b6063b 100644
--- a/pkg/compiler/lib/src/serialization/abstract_source.dart
+++ b/pkg/compiler/lib/src/serialization/abstract_source.dart
@@ -463,9 +463,12 @@
return new SetConstantValue(type, values);
case ConstantValueKind.MAP:
DartType type = readDartType();
- List<ConstantValue> keys = readConstants();
+ ListConstantValue keyList = readConstant();
List<ConstantValue> values = readConstants();
- return new MapConstantValue(type, keys, values);
+ ConstantValue protoValue = readConstantOrNull();
+ bool onlyStringKeys = readBool();
+ return new JavaScriptMapConstant(
+ type, keyList, values, protoValue, onlyStringKeys);
case ConstantValueKind.CONSTRUCTED:
InterfaceType type = readDartType();
Map<FieldEntity, ConstantValue> fields =
diff --git a/pkg/compiler/lib/src/serialization/mixins.dart b/pkg/compiler/lib/src/serialization/mixins.dart
index be891a1..385c282 100644
--- a/pkg/compiler/lib/src/serialization/mixins.dart
+++ b/pkg/compiler/lib/src/serialization/mixins.dart
@@ -256,6 +256,15 @@
}
@override
+ ConstantValue readConstantOrNull() {
+ bool hasClass = readBool();
+ if (hasClass) {
+ return readConstant();
+ }
+ return null;
+ }
+
+ @override
List<E> readConstants<E extends ConstantValue>({bool emptyAsNull: false}) {
int count = readInt();
if (count == 0 && emptyAsNull) return null;
@@ -611,6 +620,14 @@
}
@override
+ void writeConstantOrNull(ConstantValue value) {
+ writeBool(value != null);
+ if (value != null) {
+ writeConstant(value);
+ }
+ }
+
+ @override
void writeConstants(Iterable<ConstantValue> values, {bool allowNull: false}) {
if (values == null) {
assert(allowNull);
diff --git a/pkg/compiler/lib/src/serialization/serialization.dart b/pkg/compiler/lib/src/serialization/serialization.dart
index c4dab5a..74ce516 100644
--- a/pkg/compiler/lib/src/serialization/serialization.dart
+++ b/pkg/compiler/lib/src/serialization/serialization.dart
@@ -15,6 +15,7 @@
import '../elements/indexed.dart';
import '../elements/types.dart';
import '../ir/static_type_base.dart';
+import '../js_backend/constant_system_javascript.dart';
import '../js_model/closure.dart';
import '../js_model/locals.dart';
@@ -315,6 +316,9 @@
/// Writes the constant [value] to this data sink.
void writeConstant(ConstantValue value);
+ /// Writes the potentially `null` constant [value] to this data sink.
+ void writeConstantOrNull(ConstantValue value);
+
/// Writes constant [values] to this data sink. If [allowNull] is `true`,
/// [values] is allowed to be `null`.
///
@@ -626,6 +630,9 @@
/// Reads a constant value from this data source.
ConstantValue readConstant();
+ /// Reads a potentially `null` constant value from this data source.
+ ConstantValue readConstantOrNull();
+
/// Reads a double value from this data source.
double readDoubleValue();
diff --git a/pkg/compiler/lib/src/ssa/builder_kernel.dart b/pkg/compiler/lib/src/ssa/builder_kernel.dart
index 609e083..0805cef 100644
--- a/pkg/compiler/lib/src/ssa/builder_kernel.dart
+++ b/pkg/compiler/lib/src/ssa/builder_kernel.dart
@@ -554,7 +554,8 @@
// Null guard ensures an error if we are being called from an explicit
// 'new' of the constructor instead of via an upgrade. It is optimized out
// if there are field initializers.
- add(new HFieldGet(null, newObject, abstractValueDomain.dynamicType,
+ add(new HFieldGet(
+ null, newObject, abstractValueDomain.dynamicType, sourceInformation,
isAssignable: false));
for (int i = 0; i < fields.length; i++) {
add(new HFieldSet(abstractValueDomain, fields[i], newObject,
@@ -802,10 +803,11 @@
var foundSuperOrRedirectCall = false;
for (var initializer in constructor.initializers) {
if (initializer is ir.FieldInitializer) {
- // TODO(sra): Skip fields initialized in allocator.
- initializer.value.accept(this);
- constructorData.fieldValues[_elementMap.getField(initializer.field)] =
- pop();
+ FieldEntity field = _elementMap.getField(initializer.field);
+ if (!closedWorld.fieldAnalysis.isInitializedInAllocator(field)) {
+ initializer.value.accept(this);
+ constructorData.fieldValues[field] = pop();
+ }
} else if (initializer is ir.SuperInitializer) {
assert(!foundSuperOrRedirectCall);
foundSuperOrRedirectCall = true;
@@ -4761,6 +4763,11 @@
if (member == null) {
_generateSuperNoSuchMethod(node, _elementMap.getSelector(node).name,
const <HInstruction>[], const <DartType>[], sourceInformation);
+ } else if (member.isField &&
+ closedWorld.fieldAnalysis.isEffectivelyConstant(member)) {
+ ConstantValue value = closedWorld.fieldAnalysis.getConstantValue(member);
+ stack.add(graph.addConstant(value, closedWorld,
+ sourceInformation: sourceInformation));
} else {
_buildInvokeSuper(
_elementMap.getSelector(node),
@@ -5331,10 +5338,9 @@
function is! ConstructorBodyEntity &&
(mask == null ||
abstractValueDomain.isNull(mask).isPotentiallyTrue)) {
- add(new HFieldGet(
- null, providedArguments[0], abstractValueDomain.dynamicType,
- isAssignable: false)
- ..sourceInformation = sourceInformation);
+ add(new HFieldGet(null, providedArguments[0],
+ abstractValueDomain.dynamicType, sourceInformation,
+ isAssignable: false));
}
List<HInstruction> compiledArguments = _completeCallArgumentsList(
function, selector, providedArguments, currentNode);
diff --git a/pkg/compiler/lib/src/ssa/locals_handler.dart b/pkg/compiler/lib/src/ssa/locals_handler.dart
index 991cef0..b74bb8c 100644
--- a/pkg/compiler/lib/src/ssa/locals_handler.dart
+++ b/pkg/compiler/lib/src/ssa/locals_handler.dart
@@ -346,9 +346,10 @@
AbstractValue type = local is BoxLocal
? _abstractValueDomain.nonNullType
: getTypeOfCapturedVariable(redirect);
- HInstruction fieldGet = new HFieldGet(redirect, receiver, type);
+ HInstruction fieldGet =
+ new HFieldGet(redirect, receiver, type, sourceInformation);
builder.add(fieldGet);
- return fieldGet..sourceInformation = sourceInformation;
+ return fieldGet;
} else if (isBoxed(local)) {
FieldEntity redirect = redirectionMapping[local];
BoxLocal localBox;
@@ -363,10 +364,10 @@
assert(localBox != null);
HInstruction box = readLocal(localBox);
- HInstruction lookup =
- new HFieldGet(redirect, box, getTypeOfCapturedVariable(redirect));
+ HInstruction lookup = new HFieldGet(redirect, box,
+ getTypeOfCapturedVariable(redirect), sourceInformation);
builder.add(lookup);
- return lookup..sourceInformation = sourceInformation;
+ return lookup;
} else {
assert(_isUsedInTryOrGenerator(local));
HLocalValue localValue = getLocal(local);
diff --git a/pkg/compiler/lib/src/ssa/logging.dart b/pkg/compiler/lib/src/ssa/logging.dart
index 68d648f..cdaef90 100644
--- a/pkg/compiler/lib/src/ssa/logging.dart
+++ b/pkg/compiler/lib/src/ssa/logging.dart
@@ -2,6 +2,7 @@
// 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 '../elements/entities.dart';
import '../util/features.dart';
import 'nodes.dart';
@@ -28,8 +29,12 @@
void registerFieldGet(HInvokeDynamicGetter original, HFieldGet converted) {
Features features = new Features();
- features['name'] =
- '${converted.element.enclosingClass.name}.${converted.element.name}';
+ if (converted.element != null) {
+ features['name'] =
+ '${converted.element.enclosingClass.name}.${converted.element.name}';
+ } else {
+ features['name'] = '<null-guard>';
+ }
entries.add(new OptimizationLogEntry('FieldGet', features));
}
@@ -44,6 +49,33 @@
entries.add(new OptimizationLogEntry('FieldSet', features));
}
+ void registerFieldCall(HInvokeDynamicMethod original, HFieldGet converted) {
+ Features features = new Features();
+ if (converted.element != null) {
+ features['name'] =
+ '${converted.element.enclosingClass.name}.${converted.element.name}';
+ } else {
+ features['name'] = '<null-guard>';
+ }
+ entries.add(new OptimizationLogEntry('FieldCall', features));
+ }
+
+ void registerConstantFieldGet(
+ HInvokeDynamicGetter original, FieldEntity field, HConstant converted) {
+ Features features = new Features();
+ features['name'] = '${field.enclosingClass.name}.${field.name}';
+ features['value'] = converted.constant.toStructuredText();
+ entries.add(new OptimizationLogEntry('ConstantFieldGet', features));
+ }
+
+ void registerConstantFieldCall(
+ HInvokeDynamicMethod original, FieldEntity field, HConstant converted) {
+ Features features = new Features();
+ features['name'] = '${field.enclosingClass.name}.${field.name}';
+ features['value'] = converted.constant.toStructuredText();
+ entries.add(new OptimizationLogEntry('ConstantFieldCall', features));
+ }
+
Features _registerSpecializer(
HInvokeDynamic original, HInstruction converted, String name,
[String unconvertedName]) {
diff --git a/pkg/compiler/lib/src/ssa/nodes.dart b/pkg/compiler/lib/src/ssa/nodes.dart
index 69714ef..3095197 100644
--- a/pkg/compiler/lib/src/ssa/nodes.dart
+++ b/pkg/compiler/lib/src/ssa/nodes.dart
@@ -1866,10 +1866,12 @@
final bool isAssignable;
HFieldGet(FieldEntity element, HInstruction receiver, AbstractValue type,
+ SourceInformation sourceInformation,
{bool isAssignable})
: this.isAssignable =
(isAssignable != null) ? isAssignable : element.isAssignable,
super(element, <HInstruction>[receiver], type) {
+ this.sourceInformation = sourceInformation;
sideEffects.clearAllSideEffects();
sideEffects.clearAllDependencies();
setUseGvn();
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index e2e0868..f8416603 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -696,8 +696,8 @@
if (folded != node) return folded;
}
- AbstractValue receiverType =
- node.getDartReceiver(_closedWorld).instructionType;
+ HInstruction receiver = node.getDartReceiver(_closedWorld);
+ AbstractValue receiverType = receiver.instructionType;
MemberEntity element =
_closedWorld.locateSingleMember(node.selector, receiverType);
// TODO(ngeoffray): Also fold if it's a getter or variable.
@@ -734,20 +734,46 @@
FieldEntity field = element;
if (!_nativeData.isNativeMember(field) &&
!node.isCallOnInterceptor(_closedWorld)) {
- HInstruction receiver = node.getDartReceiver(_closedWorld);
- AbstractValue type = AbstractValueFactory.inferredTypeForMember(
- // ignore: UNNECESSARY_CAST
- field as Entity,
- _globalInferenceResults);
- HInstruction load = new HFieldGet(field, receiver, type);
- node.block.addBefore(node, load);
+ // Insertion point for the closure call.
+ HInstruction insertionPoint = node;
+ HInstruction load;
+ if (_closedWorld.fieldAnalysis.isEffectivelyConstant(field)) {
+ // The field is elided and replace it with its constant value.
+ if (_abstractValueDomain.isNull(receiverType).isPotentiallyTrue) {
+ // The receiver is potentially `null` so we insert a null receiver
+ // guard to trigger a null pointer exception.
+ //
+ // This could be inserted unconditionally and removed by later
+ // optimizations if unnecessary, but performance we do it
+ // conditionally here.
+ // TODO(35996): Replace with null receiver guard instruction.
+ HInstruction dummyGet = new HFieldGet(null, receiver,
+ _abstractValueDomain.dynamicType, node.sourceInformation,
+ isAssignable: false);
+ _log?.registerFieldCall(node, dummyGet);
+ node.block.addBefore(node, dummyGet);
+ insertionPoint = dummyGet;
+ }
+ ConstantValue value =
+ _closedWorld.fieldAnalysis.getConstantValue(field);
+ load = _graph.addConstant(value, _closedWorld,
+ sourceInformation: node.sourceInformation);
+ _log?.registerConstantFieldCall(node, field, load);
+ } else {
+ AbstractValue type = AbstractValueFactory.inferredTypeForMember(
+ field, _globalInferenceResults);
+ load = new HFieldGet(field, receiver, type, node.sourceInformation);
+ _log?.registerFieldCall(node, load);
+ node.block.addBefore(node, load);
+ insertionPoint = load;
+ }
Selector callSelector = new Selector.callClosureFrom(node.selector);
List<HInstruction> inputs = <HInstruction>[load]
..addAll(node.inputs.skip(node.isInterceptedCall ? 2 : 1));
HInstruction closureCall = new HInvokeClosure(
callSelector, inputs, node.instructionType, node.typeArguments)
..sourceInformation = node.sourceInformation;
- node.block.addAfter(load, closureCall);
+ node.block.addAfter(insertionPoint, closureCall);
return closureCall;
}
}
@@ -1215,17 +1241,42 @@
if (folded != node) return folded;
}
HInstruction receiver = node.getDartReceiver(_closedWorld);
+ AbstractValue receiverType = receiver.instructionType;
FieldEntity field = node.element is FieldEntity
? node.element
: findConcreteFieldForDynamicAccess(node, receiver);
if (field != null) {
- HFieldGet result = _directFieldGet(receiver, field, node);
- _log?.registerFieldGet(node, result);
- return result;
+ if (_closedWorld.fieldAnalysis.isEffectivelyConstant(field)) {
+ // The field is elided and replace it with its constant value.
+ if (_abstractValueDomain.isNull(receiverType).isPotentiallyTrue) {
+ // The receiver is potentially `null` so we insert a null receiver
+ // guard to trigger a null pointer exception.
+ //
+ // This could be inserted unconditionally and removed by later
+ // optimizations if unnecessary, but performance we do it
+ // conditionally here.
+ // TODO(35996): Replace with null receiver guard instruction.
+ HInstruction dummyGet = new HFieldGet(null, receiver,
+ _abstractValueDomain.dynamicType, node.sourceInformation,
+ isAssignable: false);
+ _log?.registerFieldGet(node, dummyGet);
+ node.block.addBefore(node, dummyGet);
+ }
+ ConstantValue constant =
+ _closedWorld.fieldAnalysis.getConstantValue(field);
+ HConstant result = _graph.addConstant(constant, _closedWorld,
+ sourceInformation: node.sourceInformation);
+ _log?.registerConstantFieldGet(node, field, result);
+ return result;
+ } else {
+ HFieldGet result = _directFieldGet(receiver, field, node);
+ _log?.registerFieldGet(node, result);
+ return result;
+ }
}
- node.element ??= _closedWorld.locateSingleMember(
- node.selector, receiver.instructionType);
+ node.element ??=
+ _closedWorld.locateSingleMember(node.selector, receiverType);
if (node.element != null &&
node.element.name == node.selector.name &&
node.element.isFunction) {
@@ -1256,8 +1307,8 @@
field, _globalInferenceResults);
}
- return new HFieldGet(field, receiver, type, isAssignable: isAssignable)
- ..sourceInformation = node.sourceInformation;
+ return new HFieldGet(field, receiver, type, node.sourceInformation,
+ isAssignable: isAssignable);
}
HInstruction visitInvokeDynamicSetter(HInvokeDynamicSetter node) {
diff --git a/tests/compiler/dart2js/codegen/gvn_test.dart b/tests/compiler/dart2js/codegen/gvn_test.dart
index 678d9fc..cdf609c 100644
--- a/tests/compiler/dart2js/codegen/gvn_test.dart
+++ b/tests/compiler/dart2js/codegen/gvn_test.dart
@@ -65,6 +65,7 @@
// Check that a gvn'able instruction in the loop header gets hoisted.
const String TEST_SIX = r"""
class A {
+ @pragma('dart2js:noElision')
final field = 54;
}
diff --git a/tests/compiler/dart2js/codegen/interceptor_test.dart b/tests/compiler/dart2js/codegen/interceptor_test.dart
index e77058e..cba680f 100644
--- a/tests/compiler/dart2js/codegen/interceptor_test.dart
+++ b/tests/compiler/dart2js/codegen/interceptor_test.dart
@@ -16,6 +16,7 @@
const String TEST_TWO = r"""
class A {
+ @pragma('dart2js:noElision')
var length;
}
foo(a) {
@@ -37,11 +38,13 @@
// intercepted, is turned into a regular getter call or field
// access.
await compile(TEST_TWO, entry: 'foo', check: (String generated) {
- Expect.isFalse(generated.contains(r'a.get$length()'));
+ Expect.isFalse(generated.contains(r'a.get$length()'),
+ 'a.get\$length() not expected in\n$generated');
+ Expect.isTrue(generated.contains(new RegExp(r'[$A-Z]+\.A\$\(\)\.length')),
+ '.length expected in\n$generated');
Expect.isTrue(
- generated.contains(new RegExp(r'[$A-Z]+\.A\$\(\)\.length')));
- Expect.isTrue(
- generated.contains(new RegExp(r'[$A-Z]+\.get\$length\$as\(a\)')));
+ generated.contains(new RegExp(r'[$A-Z]+\.get\$length\$as\(a\)')),
+ '*.get\$length expected in\n$generated');
});
}
diff --git a/tests/compiler/dart2js/codegen/model_data/dynamic_get.dart b/tests/compiler/dart2js/codegen/model_data/dynamic_get.dart
index f519f30..81c7601 100644
--- a/tests/compiler/dart2js/codegen/model_data/dynamic_get.dart
+++ b/tests/compiler/dart2js/codegen/model_data/dynamic_get.dart
@@ -14,10 +14,11 @@
class Class1a {
/*element: Class1a.field1:emitted*/
+ @pragma('dart2js:noElision')
int field1;
}
-/*element: method1:params=1*/
+/*element: method1:access=[field1],params=1*/
@pragma('dart2js:noInline')
method1(dynamic c) {
return c.field1;
@@ -25,10 +26,11 @@
class Class2a<T> {
/*element: Class2a.field2:emitted*/
+ @pragma('dart2js:noElision')
T field2;
}
-/*element: method2:params=1*/
+/*element: method2:access=[field2],params=1*/
@pragma('dart2js:noInline')
method2(dynamic c) {
return c.field2;
@@ -36,11 +38,13 @@
class Class3a {
/*element: Class3a.field3:emitted,get=simple*/
+ @pragma('dart2js:noElision')
int field3;
}
class Class3b {
/*element: Class3b.field3:emitted,get=simple*/
+ @pragma('dart2js:noElision')
int field3;
}
@@ -52,11 +56,13 @@
class Class4a {
/*element: Class4a.field4:emitted,get=simple*/
+ @pragma('dart2js:noElision')
int field4;
}
class Class4b implements Class4a {
/*element: Class4b.field4:emitted,get=simple*/
+ @pragma('dart2js:noElision')
int field4;
}
diff --git a/tests/compiler/dart2js/codegen/model_data/effectively_constant_fields.dart b/tests/compiler/dart2js/codegen/model_data/effectively_constant_fields.dart
new file mode 100644
index 0000000..fd838ae
--- /dev/null
+++ b/tests/compiler/dart2js/codegen/model_data/effectively_constant_fields.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2019, 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 'package:expect/expect.dart';
+
+/*element: _field4:params=0*/
+_field4() => 4;
+
+class Class1 {
+ /*element: Class1.field1:elided*/
+ var field1 = 0;
+
+ /*element: Class1.field2:emitted*/
+ @pragma('dart2js:noElision')
+ var field2 = 1;
+
+ /*element: Class1.field3:elided,get=simple*/
+ var field3 = 2;
+
+ /*element: Class1.field4:elided*/
+ var field4 = _field4;
+}
+
+/*element: method1:params=1*/
+@pragma('dart2js:noInline')
+method1(Class1 c) {
+ return c.field1;
+}
+
+/*element: method2:access=[field2],params=1*/
+@pragma('dart2js:noInline')
+method2(Class1 c) {
+ return c.field2;
+}
+
+class Class2 {
+ /*element: Class2.field3:elided,get=simple*/
+ final field3 = 3;
+}
+
+/*element: method3:calls=[get$field3(0)],params=1*/
+@pragma('dart2js:noInline')
+method3(c) {
+ return c.field3;
+}
+
+class Class3 extends Class1 {
+ /*element: Class3.method4:params=0*/
+ @pragma('dart2js:noInline')
+ method4() {
+ return super.field1;
+ }
+}
+
+class Class4 extends Class1 {
+ /*element: Class4.method5:calls=[_field4(0)],params=0*/
+ @pragma('dart2js:noInline')
+ method5() {
+ return super.field4();
+ }
+}
+
+/*element: method6:access=[toString],params=1*/
+@pragma('dart2js:noInline')
+method6(Class1 c) {
+ return c.field1;
+}
+
+/*element: method7:access=[toString],calls=[_field4(0)],params=1*/
+@pragma('dart2js:noInline')
+method7(Class1 c) {
+ return c.field4();
+}
+
+/*element: main:calls=*,params=0*/
+main() {
+ Expect.equals(0, method1(new Class1()));
+ Expect.equals(1, method2(new Class1()));
+ Expect.equals(2, method3(new Class1()));
+ Expect.equals(3, method3(new Class2()));
+ Expect.equals(0, new Class3().method4());
+ Expect.equals(4, new Class4().method5());
+ Expect.equals(0, method6(new Class1()));
+ Expect.throws(/*calls=[method6(1)],params=0*/ () => method6(null));
+ Expect.equals(4, method7(new Class1()));
+ Expect.throws(/*calls=[method7(1)],params=0*/ () => method7(null));
+}
diff --git a/tests/compiler/dart2js/codegen/model_data/field_set.dart b/tests/compiler/dart2js/codegen/model_data/field_set.dart
index 6c23abe..2c68409 100644
--- a/tests/compiler/dart2js/codegen/model_data/field_set.dart
+++ b/tests/compiler/dart2js/codegen/model_data/field_set.dart
@@ -2,7 +2,7 @@
// 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.
-/*element: main:calls=*,params=0*/
+/*element: main:access=*,calls=*,params=0*/
main() {
method1(new Class1a());
method2(new Class2a());
diff --git a/tests/compiler/dart2js/codegen/model_data/fields.dart b/tests/compiler/dart2js/codegen/model_data/fields.dart
index 62c3b52..7815fbe 100644
--- a/tests/compiler/dart2js/codegen/model_data/fields.dart
+++ b/tests/compiler/dart2js/codegen/model_data/fields.dart
@@ -32,6 +32,7 @@
class Class {
/*element: Class.field1a:emitted*/
+ @pragma('dart2js:noElision')
var field1a;
/*element: Class.field1b:elided*/
diff --git a/tests/compiler/dart2js/codegen/model_data/native_unused_parameters.dart b/tests/compiler/dart2js/codegen/model_data/native_unused_parameters.dart
index 36197db..9317686 100644
--- a/tests/compiler/dart2js/codegen/model_data/native_unused_parameters.dart
+++ b/tests/compiler/dart2js/codegen/model_data/native_unused_parameters.dart
@@ -5,7 +5,7 @@
// ignore: import_internal_library
import 'dart:_js_helper';
-/*element: Class.:params=1*/
+/*element: Class.:access=[toString],params=1*/
@Native('Class')
class Class {
/*element: Class.method1:calls=[method1()],params=1*/
diff --git a/tests/compiler/dart2js/codegen/model_test.dart b/tests/compiler/dart2js/codegen/model_test.dart
index e00c78d..974ca4a 100644
--- a/tests/compiler/dart2js/codegen/model_test.dart
+++ b/tests/compiler/dart2js/codegen/model_test.dart
@@ -63,6 +63,7 @@
static const String isElided = 'elided';
static const String assignment = 'assign';
static const String isLazy = 'lazy';
+ static const String propertyAccess = 'access';
}
/// AST visitor for computing inference data for a member.
@@ -133,6 +134,8 @@
features[Tags.parameterCount] = '${code.params.length}';
}
+ Set<js.PropertyAccess> handledAccesses = new Set();
+
void registerCalls(String tag, js.Node node, [String prefix = '']) {
forEachNode(node, onCall: (js.Call node) {
js.Node target = node.target;
@@ -159,6 +162,7 @@
features.addElement(
tag, '${prefix}${name}(${node.arguments.length})');
}
+ handledAccesses.add(target);
}
}
});
@@ -170,6 +174,7 @@
registerCalls(Tags.parameterStub, stub.code, '${stub.name.key}:');
}
}
+
forEachNode(code, onAssignment: (js.Assignment node) {
js.Expression leftHandSide = node.leftHandSide;
if (leftHandSide is js.PropertyAccess) {
@@ -182,9 +187,41 @@
}
if (name != null) {
features.addElement(Tags.assignment, '${name}');
+ handledAccesses.add(leftHandSide);
}
}
});
+
+ forEachNode(code, onPropertyAccess: (js.PropertyAccess node) {
+ if (handledAccesses.contains(node)) {
+ return;
+ }
+
+ js.Node receiver = node.receiver;
+ String receiverName;
+ if (receiver is js.VariableUse) {
+ receiverName = receiver.name;
+ if (receiverName == receiverName.toUpperCase()) {
+ // Skip holder access.
+ receiverName = null;
+ }
+ }
+
+ js.Node selector = node.selector;
+ String name;
+ if (selector is js.Name) {
+ name = selector.key;
+ } else if (selector is js.LiteralString) {
+ /// Call to fixed backend name, so we include the argument
+ /// values to test encoding of optional parameters in native
+ /// methods.
+ name = selector.value.substring(1, selector.value.length - 1);
+ }
+
+ if (receiverName != null && name != null) {
+ features.addElement(Tags.propertyAccess, '${name}');
+ }
+ });
return features;
}
}
diff --git a/tests/compiler/dart2js/field_analysis/jdata/multi_initializers.dart b/tests/compiler/dart2js/field_analysis/jdata/multi_initializers.dart
index 3a71206..d72731d 100644
--- a/tests/compiler/dart2js/field_analysis/jdata/multi_initializers.dart
+++ b/tests/compiler/dart2js/field_analysis/jdata/multi_initializers.dart
@@ -3,8 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
main() {
- new Class1.a();
+ var c = new Class1.a();
+ c.field4a = null;
new Class1.b();
+
+ print(c.field1);
+ print(c.field2);
+ print(c.field3);
+ print(c.field4a);
+ print(c.field4b);
+ print(c.field5);
}
class Class1 {
@@ -12,8 +20,11 @@
var field2;
var field3;
- /*element: Class1.field4:initial=IntConstant(4)*/
- var field4 = 4;
+ /*element: Class1.field4a:initial=IntConstant(4)*/
+ var field4a = 4;
+
+ /*element: Class1.field4b:constant=IntConstant(4)*/
+ var field4b = 4;
var field5 = 5;
diff --git a/tests/compiler/dart2js/field_analysis/jdata/simple_initializers.dart b/tests/compiler/dart2js/field_analysis/jdata/simple_initializers.dart
index 05a5f22..0a5a612 100644
--- a/tests/compiler/dart2js/field_analysis/jdata/simple_initializers.dart
+++ b/tests/compiler/dart2js/field_analysis/jdata/simple_initializers.dart
@@ -3,108 +3,336 @@
// BSD-style license that can be found in the LICENSE file.
main() {
- new Class1();
- new Class2();
+ use1(new Class1());
+ use2(new Class2());
+}
+
+@pragma('dart2js:noInline')
+use(Object o) {
+ print(o);
+}
+
+@pragma('dart2js:noInline')
+use1(Class1 c1) {
+ c1.field0a = null;
+ c1.field1a = null;
+ c1.field2a = null;
+ c1.field3a = null;
+ c1.field4a = null;
+ c1.field5a = null;
+ c1.field6a = null;
+ c1.field7a = null;
+ c1.field8a = null;
+ c1.field9a = null;
+ c1.field9c = null;
+ c1.field10a = null;
+ c1.field10c = null;
+ c1.field11a = null;
+ c1.field12a = null;
+ c1.field13a = null;
+ use(c1.field0a);
+ use(c1.field0b);
+ use(c1.field1a);
+ use(c1.field1b);
+ use(c1.field2a);
+ use(c1.field2b);
+ use(c1.field3a);
+ use(c1.field3b);
+ use(c1.field4a);
+ use(c1.field4b);
+ use(c1.field5a);
+ use(c1.field5b);
+ use(c1.field6a);
+ use(c1.field6b);
+ use(c1.field7a);
+ use(c1.field7b);
+ use(c1.field8a);
+ use(c1.field8b);
+ use(c1.field9a);
+ use(c1.field9b);
+ use(c1.field9c);
+ use(c1.field9d);
+ use(c1.field10a);
+ use(c1.field10b);
+ use(c1.field10c);
+ use(c1.field10d);
+ use(c1.field11a);
+ use(c1.field11b);
+ use(c1.field12a);
+ use(c1.field12b);
+ use(c1.field13a);
+ use(c1.field13b);
+}
+
+@pragma('dart2js:noInline')
+use2(Class2 c2) {
+ c2.field1a = null;
+ c2.field2a = null;
+ c2.field3a = null;
+ c2.field4a = null;
+ c2.field5a = null;
+ c2.field6a = null;
+ c2.field7a = null;
+ c2.field8a = null;
+ c2.field9a = null;
+ c2.field9c = null;
+ c2.field10a = null;
+ c2.field10c = null;
+ c2.field11a = null;
+ c2.field12a = null;
+ c2.field13a = null;
+ use(c2.field1a);
+ use(c2.field1b);
+ use(c2.field2a);
+ use(c2.field2b);
+ use(c2.field3a);
+ use(c2.field3b);
+ use(c2.field4a);
+ use(c2.field4b);
+ use(c2.field5a);
+ use(c2.field5b);
+ use(c2.field6a);
+ use(c2.field6b);
+ use(c2.field7a);
+ use(c2.field7b);
+ use(c2.field8a);
+ use(c2.field8b);
+ use(c2.field9a);
+ use(c2.field9b);
+ use(c2.field9c);
+ use(c2.field9d);
+ use(c2.field10a);
+ use(c2.field10b);
+ use(c2.field10c);
+ use(c2.field10d);
+ use(c2.field11a);
+ use(c2.field11b);
+ use(c2.field12a);
+ use(c2.field12b);
+ use(c2.field13a);
+ use(c2.field13b);
}
const bool const1 = true;
class Class1 {
- /*element: Class1.field0:initial=NullConstant*/
- var field0;
+ /*element: Class1.field0a:initial=NullConstant*/
+ var field0a;
- /*element: Class1.field1:initial=NullConstant*/
- var field1 = null;
+ /*element: Class1.field0b:constant=NullConstant*/
+ var field0b;
- /*element: Class1.field2:initial=BoolConstant(true)*/
- var field2 = true;
+ /*element: Class1.field1a:initial=NullConstant*/
+ var field1a = null;
- /*element: Class1.field3:initial=BoolConstant(false)*/
- var field3 = false;
+ /*element: Class1.field1b:constant=NullConstant*/
+ var field1b = null;
- /*element: Class1.field4:initial=IntConstant(0)*/
- var field4 = 0;
+ /*element: Class1.field2a:initial=BoolConstant(true)*/
+ var field2a = true;
- /*element: Class1.field5:initial=IntConstant(1)*/
- var field5 = 1;
+ /*element: Class1.field2b:constant=BoolConstant(true)*/
+ var field2b = true;
- /*element: Class1.field6:initial=StringConstant("")*/
- var field6 = '';
+ /*element: Class1.field3a:initial=BoolConstant(false)*/
+ var field3a = false;
- /*element: Class1.field7:initial=StringConstant("foo")*/
- var field7 = 'foo';
+ /*element: Class1.field3b:constant=BoolConstant(false)*/
+ var field3b = false;
- /*element: Class1.field8:*/
- var field8 = 0.5;
+ /*element: Class1.field4a:initial=IntConstant(0)*/
+ var field4a = 0;
- /*element: Class1.field9:*/
- var field9 = const [];
+ /*element: Class1.field4b:constant=IntConstant(0)*/
+ var field4b = 0;
- /*element: Class1.field10:*/
- var field10 = const {};
+ /*element: Class1.field5a:initial=IntConstant(1)*/
+ var field5a = 1;
- /*element: Class1.field11:*/
- var field11 = #foo;
+ /*element: Class1.field5b:constant=IntConstant(1)*/
+ var field5b = 1;
- /*element: Class1.field12:initial=IntConstant(5)*/
- var field12 = 2 + 3;
+ /*element: Class1.field6a:initial=StringConstant("")*/
+ var field6a = '';
- /*element: Class1.field13:initial=BoolConstant(true)*/
- var field13 = const1;
+ /*element: Class1.field6b:constant=StringConstant("")*/
+ var field6b = '';
+
+ /*element: Class1.field7a:initial=StringConstant("foo")*/
+ var field7a = 'foo';
+
+ /*element: Class1.field7b:constant=StringConstant("foo")*/
+ var field7b = 'foo';
+
+ /*element: Class1.field8a:*/
+ var field8a = 0.5;
+
+ /*element: Class1.field8b:constant=DoubleConstant(0.5)*/
+ var field8b = 0.5;
+
+ /*element: Class1.field9a:*/
+ var field9a = const [];
+
+ /*element: Class1.field9b:constant=ListConstant([])*/
+ var field9b = const [];
+
+ /*element: Class1.field9c:*/
+ var field9c = const [0, 1];
+
+ /*element: Class1.field9d:constant=ListConstant(<int>[IntConstant(0), IntConstant(1), IntConstant(2)])*/
+ var field9d = const [0, 1, 2];
+
+ /*element: Class1.field10a:*/
+ var field10a = const {};
+
+ /*element: Class1.field10b:constant=MapConstant({})*/
+ var field10b = const {};
+
+ /*element: Class1.field10c:*/
+ var field10c = const {0: 1, 2: 3};
+
+ /*element: Class1.field10d:constant=MapConstant(<int, int>{IntConstant(0): IntConstant(1), IntConstant(2): IntConstant(3), IntConstant(4): IntConstant(5)})*/
+ var field10d = const {0: 1, 2: 3, 4: 5};
+
+ /*element: Class1.field11a:*/
+ var field11a = #foo;
+
+ /*element: Class1.field11b:constant=ConstructedConstant(Symbol(_name=StringConstant("foo")))*/
+ var field11b = #foo;
+
+ /*element: Class1.field12a:initial=IntConstant(5)*/
+ var field12a = 2 + 3;
+
+ /*element: Class1.field12b:constant=IntConstant(5)*/
+ var field12b = 2 + 3;
+
+ /*element: Class1.field13a:initial=BoolConstant(true)*/
+ var field13a = const1;
+
+ /*element: Class1.field13b:constant=BoolConstant(true)*/
+ var field13b = const1;
}
class Class2 {
- /*element: Class2.field1:*/
- var field1;
+ /*element: Class2.field1a:*/
+ var field1a;
- /*element: Class2.field2:*/
- var field2;
+ /*element: Class2.field1b:*/
+ var field1b;
- /*element: Class2.field3:*/
- var field3;
+ /*element: Class2.field2a:*/
+ var field2a;
- /*element: Class2.field4:*/
- var field4;
+ /*element: Class2.field2b:*/
+ var field2b;
- /*element: Class2.field5:*/
- var field5;
+ /*element: Class2.field3a:*/
+ var field3a;
- /*element: Class2.field6:*/
- var field6;
+ /*element: Class2.field3b:*/
+ var field3b;
- /*element: Class2.field7:*/
- var field7;
+ /*element: Class2.field4a:*/
+ var field4a;
- /*element: Class2.field8:*/
- var field8;
+ /*element: Class2.field4b:*/
+ var field4b;
- /*element: Class2.field9:*/
- var field9;
+ /*element: Class2.field5a:*/
+ var field5a;
- /*element: Class2.field10:*/
- var field10;
+ /*element: Class2.field5b:*/
+ var field5b;
- /*element: Class2.field11:*/
- var field11;
+ /*element: Class2.field6a:*/
+ var field6a;
- /*element: Class2.field12:*/
- var field12;
+ /*element: Class2.field6b:*/
+ var field6b;
- /*element: Class2.field13:*/
- var field13;
+ /*element: Class2.field7a:*/
+ var field7a;
+
+ /*element: Class2.field7b:*/
+ var field7b;
+
+ /*element: Class2.field8a:*/
+ var field8a;
+
+ /*element: Class2.field8b:*/
+ var field8b;
+
+ /*element: Class2.field9a:*/
+ var field9a;
+
+ /*element: Class2.field9b:*/
+ var field9b;
+
+ /*element: Class2.field9c:*/
+ var field9c;
+
+ /*element: Class2.field9d:*/
+ var field9d;
+
+ /*element: Class2.field10a:*/
+ var field10a;
+
+ /*element: Class2.field10b:*/
+ var field10b;
+
+ /*element: Class2.field10c:*/
+ var field10c;
+
+ /*element: Class2.field10d:*/
+ var field10d;
+
+ /*element: Class2.field11a:*/
+ var field11a;
+
+ /*element: Class2.field11b:*/
+ var field11b;
+
+ /*element: Class2.field12a:*/
+ var field12a;
+
+ /*element: Class2.field12b:*/
+ var field12b;
+
+ /*element: Class2.field13a:*/
+ var field13a;
+
+ /*element: Class2.field13b:*/
+ var field13b;
Class2()
- : field1 = null,
- field2 = true,
- field3 = false,
- field4 = 0,
- field5 = 1,
- field6 = '',
- field7 = 'foo',
- field8 = 0.5,
- field9 = const [],
- field10 = const {},
- field11 = #foo,
- field12 = 2 + 3,
- field13 = const1;
+ : field1a = null,
+ field1b = null,
+ field2a = true,
+ field2b = true,
+ field3a = false,
+ field3b = false,
+ field4a = 0,
+ field4b = 0,
+ field5a = 1,
+ field5b = 1,
+ field6a = '',
+ field6b = '',
+ field7a = 'foo',
+ field7b = 'foo',
+ field8a = 0.5,
+ field8b = 0.5,
+ field9a = const [],
+ field9b = const [],
+ field9c = const [0, 1],
+ field9d = const [0, 1, 2],
+ field10a = const {},
+ field10b = const {},
+ field10c = const {0: 1, 2: 3},
+ field10d = const {0: 1, 2: 3, 4: 5},
+ field11a = #foo,
+ field11b = #foo,
+ field12a = 2 + 3,
+ field12b = 2 + 3,
+ field13a = const1,
+ field13b = const1;
}
diff --git a/tests/compiler/dart2js/field_analysis/jfield_analysis_test.dart b/tests/compiler/dart2js/field_analysis/jfield_analysis_test.dart
index 207863e..ebfb525 100644
--- a/tests/compiler/dart2js/field_analysis/jfield_analysis_test.dart
+++ b/tests/compiler/dart2js/field_analysis/jfield_analysis_test.dart
@@ -5,7 +5,6 @@
import 'dart:io';
import 'package:async_helper/async_helper.dart';
import 'package:compiler/src/compiler.dart';
-import 'package:compiler/src/constants/values.dart';
import 'package:compiler/src/elements/entities.dart';
import 'package:compiler/src/ir/util.dart';
import 'package:compiler/src/js_backend/field_analysis.dart';
@@ -25,6 +24,7 @@
class Tags {
static const String initialValue = 'initial';
+ static const String constantValue = 'constant';
}
class JAllocatorAnalysisDataComputer extends DataComputer<Features> {
@@ -38,10 +38,14 @@
JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
JFieldAnalysis allocatorAnalysis = closedWorld.fieldAnalysis;
ir.Member node = closedWorld.elementMap.getMemberDefinition(member).node;
- ConstantValue initialValue = allocatorAnalysis.initializerValue(member);
Features features = new Features();
- if (initialValue != null) {
- features[Tags.initialValue] = initialValue.toStructuredText();
+ if (allocatorAnalysis.isInitializedInAllocator(member)) {
+ features[Tags.initialValue] =
+ allocatorAnalysis.initializerValue(member).toStructuredText();
+ }
+ if (allocatorAnalysis.isEffectivelyConstant(member)) {
+ features[Tags.constantValue] =
+ allocatorAnalysis.getConstantValue(member).toStructuredText();
}
Id id = computeEntityId(node);
actualMap[id] = new ActualData<Features>(
diff --git a/tests/compiler/dart2js/model/constant_expression_evaluate_test.dart b/tests/compiler/dart2js/model/constant_expression_evaluate_test.dart
index c35cadf..0660b7f 100644
--- a/tests/compiler/dart2js/model/constant_expression_evaluate_test.dart
+++ b/tests/compiler/dart2js/model/constant_expression_evaluate_test.dart
@@ -14,10 +14,10 @@
import 'package:compiler/src/constants/evaluation.dart';
import 'package:compiler/src/constants/expressions.dart';
import 'package:compiler/src/constants/values.dart';
-import 'package:compiler/src/constant_system_dart.dart';
import 'package:compiler/src/diagnostics/messages.dart';
import 'package:compiler/src/elements/entities.dart';
import 'package:compiler/src/elements/types.dart';
+import 'package:compiler/src/js_backend/constant_system_javascript.dart';
import 'package:compiler/src/kernel/kernel_strategy.dart';
import 'package:compiler/src/kernel/element_map_impl.dart';
import '../helpers/memory_compiler.dart';
@@ -146,7 +146,8 @@
const ConstantData('false', 'BoolConstant(false)'),
const ConstantData('true', 'BoolConstant(true)'),
const ConstantData('0', 'IntConstant(0)'),
- const ConstantData('0.0', 'DoubleConstant(0.0)'),
+ const ConstantData('0.0', 'IntConstant(0)'),
+ const ConstantData('0.5', 'DoubleConstant(0.5)'),
const ConstantData('"foo"', 'StringConstant("foo")'),
const ConstantData('1 + 2', 'IntConstant(3)'),
const ConstantData('-(1)', 'IntConstant(-1)'),
@@ -633,7 +634,7 @@
MemoryEnvironment environment =
new MemoryEnvironment(getEnvironment(compiler, field), env);
ConstantValue value =
- constant.evaluate(environment, DART_CONSTANT_SYSTEM);
+ constant.evaluate(environment, JavaScriptConstantSystem.only);
Expect.isNotNull(
value,
diff --git a/tests/compiler/dart2js/optimization/data/effectively_constant_fields.dart b/tests/compiler/dart2js/optimization/data/effectively_constant_fields.dart
new file mode 100644
index 0000000..70578fc
--- /dev/null
+++ b/tests/compiler/dart2js/optimization/data/effectively_constant_fields.dart
@@ -0,0 +1,83 @@
+// Copyright (c) 2019, 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 'package:expect/expect.dart';
+
+class Class1 {
+ var field1 = 0;
+
+ @pragma('dart2js:noElision')
+ var field2 = 0;
+
+ var field3 = 0;
+}
+
+/*element: method1:
+ ConstantFieldGet=[name=Class1.field1&value=IntConstant(0)],
+ FieldGet=[]
+*/
+@pragma('dart2js:noInline')
+method1(Class1 c) {
+ return c.field1;
+}
+
+/*element: method2:FieldGet=[name=Class1.field2]*/
+@pragma('dart2js:noInline')
+method2(Class1 c) {
+ return c.field2;
+}
+
+class Class2 {
+ var field3 = 0;
+}
+
+@pragma('dart2js:noInline')
+method3(c) {
+ return c.field3;
+}
+
+int _field4() => 0;
+
+class Class3 {
+ int Function() field4 = _field4;
+}
+
+/*element: method4:
+ ConstantFieldCall=[name=Class3.field4&value=FunctionConstant(_field4)],
+ FieldCall=[]
+*/
+@pragma('dart2js:noInline')
+method4(Class3 c) {
+ return c.field4();
+}
+
+/*element: method6:
+ ConstantFieldGet=[name=Class1.field1&value=IntConstant(0)],
+ FieldGet=[name=<null-guard>]
+*/
+@pragma('dart2js:noInline')
+method6(Class1 c) {
+ return c.field1;
+}
+
+/*element: method7:
+ ConstantFieldCall=[name=Class3.field4&value=FunctionConstant(_field4)],
+ FieldCall=[name=<null-guard>]
+*/
+@pragma('dart2js:noInline')
+method7(Class3 c) {
+ return c.field4();
+}
+
+main() {
+ Expect.equals(0, method1(new Class1()));
+ Expect.equals(0, method2(new Class1()));
+ Expect.equals(0, method3(new Class1()));
+ Expect.equals(0, method3(new Class2()));
+ Expect.equals(0, method4(new Class3()));
+ Expect.equals(0, method6(new Class1()));
+ Expect.throws(() => method6(null));
+ Expect.equals(4, method7(new Class3()));
+ Expect.throws(() => method7(null));
+}
diff --git a/tests/compiler/dart2js/optimization/data/field_get.dart b/tests/compiler/dart2js/optimization/data/field_get.dart
index 349edca..e9101e5 100644
--- a/tests/compiler/dart2js/optimization/data/field_get.dart
+++ b/tests/compiler/dart2js/optimization/data/field_get.dart
@@ -10,9 +10,11 @@
method3(new Class3b());
method4(new Class4a());
method4(new Class4b());
+ method5(new Class5a());
}
class Class1a {
+ @pragma('dart2js:noElision')
int field1;
}
@@ -23,6 +25,7 @@
}
class Class2a {
+ @pragma('dart2js:noElision')
int field2;
}
@@ -60,3 +63,14 @@
method4(Class4a c) {
return c.field4;
}
+
+class Class5a {
+ @pragma('dart2js:noElision')
+ int Function() field5;
+}
+
+/*element: method5:FieldCall=[name=Class5a.field5]*/
+@pragma('dart2js:noInline')
+method5(Class5a c) {
+ return c.field5();
+}
diff --git a/tests/compiler/dart2js/sourcemaps/stacktrace/null_instance_field.dart b/tests/compiler/dart2js/sourcemaps/stacktrace/null_instance_field.dart
index a909569..3788fd0 100644
--- a/tests/compiler/dart2js/sourcemaps/stacktrace/null_instance_field.dart
+++ b/tests/compiler/dart2js/sourcemaps/stacktrace/null_instance_field.dart
@@ -14,5 +14,6 @@
}
class Class {
+ @pragma('dart2js:noElision')
var field;
}
diff --git a/tests/compiler/dart2js/sourcemaps/stacktrace/null_instance_field_elided.dart b/tests/compiler/dart2js/sourcemaps/stacktrace/null_instance_field_elided.dart
new file mode 100644
index 0000000..832d6ad
--- /dev/null
+++ b/tests/compiler/dart2js/sourcemaps/stacktrace/null_instance_field_elided.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2019, 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 'package:expect/expect.dart';
+
+main() {
+ /*1:main*/ test(new Class());
+}
+
+@NoInline()
+test(c) {
+ c.field. /*2:test*/ method();
+}
+
+class Class {
+ var field;
+}
diff --git a/tests/compiler/dart2js/sourcemaps/stacktrace/sync_throw_in_async.dart b/tests/compiler/dart2js/sourcemaps/stacktrace/sync_throw_in_async.dart
index 13e5ecd..79a0fc9 100644
--- a/tests/compiler/dart2js/sourcemaps/stacktrace/sync_throw_in_async.dart
+++ b/tests/compiler/dart2js/sourcemaps/stacktrace/sync_throw_in_async.dart
@@ -9,7 +9,8 @@
/*1:main*/ test();
}
+// TODO(34942): Step 2 should point to the body block.
@NoInline()
-test() async /*2:test*/ {
+test /*2:test*/ () async {
/*4:test*/ throw '>ExceptionMarker<';
}
diff --git a/tests/compiler/dart2js/sourcemaps/stacktrace/sync_throw_in_awaited_async.dart b/tests/compiler/dart2js/sourcemaps/stacktrace/sync_throw_in_awaited_async.dart
index 49c7a18..41e377e 100644
--- a/tests/compiler/dart2js/sourcemaps/stacktrace/sync_throw_in_awaited_async.dart
+++ b/tests/compiler/dart2js/sourcemaps/stacktrace/sync_throw_in_awaited_async.dart
@@ -9,13 +9,15 @@
/*1:main*/ test1();
}
+// TODO(34942): Step 3 should point to the body block.
@NoInline()
-test1() async /*3:test1*/ {
+test1 /*3:test1*/ () async {
// This call is on the stack when the error is thrown.
await /*5:test1*/ test2();
}
+// TODO(34942): Step 7 should point to the body block.
@NoInline()
-test2() async /*7:test2*/ {
+test2 /*7:test2*/ () async {
/*9:test2*/ throw '>ExceptionMarker<';
}
diff --git a/tests/compiler/dart2js/sourcemaps/stacktrace/sync_throw_in_top_level_method_from_async.dart b/tests/compiler/dart2js/sourcemaps/stacktrace/sync_throw_in_top_level_method_from_async.dart
index 7ea1cf9..69e9741 100644
--- a/tests/compiler/dart2js/sourcemaps/stacktrace/sync_throw_in_top_level_method_from_async.dart
+++ b/tests/compiler/dart2js/sourcemaps/stacktrace/sync_throw_in_top_level_method_from_async.dart
@@ -8,8 +8,9 @@
/*1:main*/ test1();
}
+// TODO(34942): Step 2 should point to the body block.
@NoInline()
-test1() async /*2:test1*/ {
+test1 /*2:test1*/ () async {
/*9:test1*/ test2();
}
diff --git a/tests/compiler/dart2js/sourcemaps/stacktrace_test.dart b/tests/compiler/dart2js/sourcemaps/stacktrace_test.dart
index 5d4d3b7..a4a6fa0 100644
--- a/tests/compiler/dart2js/sourcemaps/stacktrace_test.dart
+++ b/tests/compiler/dart2js/sourcemaps/stacktrace_test.dart
@@ -8,6 +8,7 @@
import 'package:args/args.dart';
import 'package:async_helper/async_helper.dart';
import 'package:compiler/compiler_new.dart';
+import 'package:compiler/src/commandline_options.dart';
import 'package:compiler/src/dart2js.dart' as entry;
import 'package:sourcemap_testing/src/stacktrace_helper.dart';
@@ -83,6 +84,7 @@
'-o$output',
'--libraries-spec=sdk/lib/libraries.json',
'--packages=${Platform.packageConfig}',
+ Flags.testMode,
input,
]..addAll(options);
print("Compiling dart2js ${arguments.join(' ')}");
diff --git a/tests/compiler/dart2js_extra/effectively_constant_fields_test.dart b/tests/compiler/dart2js_extra/effectively_constant_fields_test.dart
new file mode 100644
index 0000000..6c9c18b
--- /dev/null
+++ b/tests/compiler/dart2js_extra/effectively_constant_fields_test.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2019, 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 'package:expect/expect.dart';
+
+_field4() => 4;
+
+class Class1 {
+ var field1 = 0;
+
+ @pragma('dart2js:noElision')
+ var field2 = 1;
+
+ var field3 = 2;
+
+ var field4 = _field4;
+}
+
+@pragma('dart2js:noInline')
+method1(Class1 c) {
+ return c.field1;
+}
+
+@pragma('dart2js:noInline')
+method2(Class1 c) {
+ return c.field2;
+}
+
+class Class2 {
+ var field3 = 3;
+}
+
+@pragma('dart2js:noInline')
+method3(c) {
+ return c.field3;
+}
+
+class Class3 extends Class1 {
+ @pragma('dart2js:noInline')
+ method4() {
+ return super.field1;
+ }
+}
+
+class Class4 extends Class1 {
+ @pragma('dart2js:noInline')
+ method5() {
+ return super.field4();
+ }
+}
+
+@pragma('dart2js:noInline')
+method6(Class1 c) {
+ return c.field1;
+}
+
+@pragma('dart2js:noInline')
+method7(Class1 c) {
+ return c.field4();
+}
+
+main() {
+ Expect.equals(0, method1(new Class1()));
+ Expect.equals(1, method2(new Class1()));
+ Expect.equals(2, method3(new Class1()));
+ Expect.equals(3, method3(new Class2()));
+ Expect.equals(0, new Class3().method4());
+ Expect.equals(4, new Class4().method5());
+ Expect.equals(0, method6(new Class1()));
+ Expect.throws(() => method6(null));
+ Expect.equals(4, method7(new Class1()));
+ Expect.throws(() => method7(null));
+}