Version 2.14.0-329.0.dev
Merge commit '885709755699276d5ceb7a4434443a6ccfcab469' into 'dev'
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index be8938e..eb40ba7 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -11,7 +11,7 @@
"constraint, update this by running tools/generate_package_config.dart."
],
"configVersion": 2,
- "generated": "2021-07-14T10:43:41.119864",
+ "generated": "2021-07-15T08:50:03.843620",
"generator": "tools/generate_package_config.dart",
"packages": [
{
diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart
index 2bdca7a..740d067 100644
--- a/pkg/analyzer/lib/src/dart/element/element.dart
+++ b/pkg/analyzer/lib/src/dart/element/element.dart
@@ -610,6 +610,42 @@
@override
bool get isDartCoreObject => !isMixin && supertype == null;
+ bool get isEnumLike {
+ // Must be a concrete class.
+ if (isAbstract || isMixin) {
+ return false;
+ }
+
+ // With only private non-factory constructors.
+ for (var constructor in constructors) {
+ if (constructor.isPublic || constructor.isFactory) {
+ return false;
+ }
+ }
+
+ // With 2+ static const fields with the type of this class.
+ var numberOfElements = 0;
+ for (var field in fields) {
+ if (field.isStatic && field.isConst && field.type == thisType) {
+ numberOfElements++;
+ }
+ }
+ if (numberOfElements < 2) {
+ return false;
+ }
+
+ // No subclasses in the library.
+ for (var unit in library.units) {
+ for (var class_ in unit.classes) {
+ if (class_.supertype?.element == this) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
@override
bool get isMixinApplication {
return hasModifier(Modifier.MIXIN_APPLICATION);
diff --git a/pkg/analyzer/test/src/dart/element/class_element_test.dart b/pkg/analyzer/test/src/dart/element/class_element_test.dart
index dbe96a2..c6ed690 100644
--- a/pkg/analyzer/test/src/dart/element/class_element_test.dart
+++ b/pkg/analyzer/test/src/dart/element/class_element_test.dart
@@ -3,7 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/error/codes.dart';
+import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../resolution/context_collection_resolution.dart';
@@ -16,6 +18,132 @@
@reflectiveTest
class ClassElementTest extends PubPackageResolutionTest {
+ test_isEnumLike_false_constructors_hasFactory() async {
+ await assertNoErrorsInCode('''
+class A {
+ factory A._foo() => A._bar();
+ A._bar();
+}
+
+void f() {
+ A._foo();
+}
+''');
+ _assertIsEnumLike(findElement.class_('A'), false);
+ }
+
+ test_isEnumLike_false_constructors_hasPublic() async {
+ await assertNoErrorsInCode('''
+class A {}
+''');
+ _assertIsEnumLike(findElement.class_('A'), false);
+ }
+
+ test_isEnumLike_false_fields_empty() async {
+ await assertNoErrorsInCode('''
+class A {
+ const A._();
+}
+
+void f() {
+ A._();
+}
+''');
+ _assertIsEnumLike(findElement.class_('A'), false);
+ }
+
+ test_isEnumLike_false_isAbstract() async {
+ await assertNoErrorsInCode('''
+abstract class A {}
+''');
+ _assertIsEnumLike(findElement.class_('A'), false);
+ }
+
+ test_isEnumLike_false_isExtended() async {
+ await assertNoErrorsInCode('''
+class A {
+ static const one = A._();
+ static const two = A._();
+ const A._();
+}
+
+class B extends A {
+ B() : super._();
+}
+''');
+ _assertIsEnumLike(findElement.class_('A'), false);
+ }
+
+ test_isEnumLike_false_isMixin() async {
+ await assertNoErrorsInCode('''
+mixin M {}
+''');
+ _assertIsEnumLike(findElement.mixin('M'), false);
+ }
+
+ test_isEnumLike_true() async {
+ await assertNoErrorsInCode('''
+class A {
+ static const one = A._();
+ static const two = A._();
+ const A._();
+}
+''');
+ _assertIsEnumLike(findElement.class_('A'), true);
+ }
+
+ test_isEnumLike_true_hasInstanceField() async {
+ await assertNoErrorsInCode('''
+class A {
+ static const one = A._(1);
+ static const two = A._(2);
+ final int f;
+ const A._(this.f);
+}
+''');
+ _assertIsEnumLike(findElement.class_('A'), true);
+ }
+
+ test_isEnumLike_true_hasSuperclass() async {
+ await assertNoErrorsInCode('''
+class A {
+ const A();
+}
+
+class B extends A {
+ static const one = B._();
+ static const two = B._();
+ const B._();
+}
+''');
+ _assertIsEnumLike(findElement.class_('B'), true);
+ }
+
+ test_isEnumLike_true_hasSyntheticField() async {
+ await assertNoErrorsInCode('''
+class A {
+ static const one = A._();
+ static const two = A._();
+ const A._();
+ int get foo => 0;
+}
+''');
+ _assertIsEnumLike(findElement.class_('A'), true);
+ }
+
+ test_isEnumLike_true_isImplemented() async {
+ await assertNoErrorsInCode('''
+class A {
+ static const one = A._();
+ static const two = A._();
+ const A._();
+}
+
+class B implements A {}
+''');
+ _assertIsEnumLike(findElement.class_('A'), true);
+ }
+
test_lookUpInheritedConcreteGetter_declared() async {
await assertNoErrorsInCode('''
class A {
@@ -1196,6 +1324,10 @@
A._lookUpInheritedMethod('foo'),
);
}
+
+ static void _assertIsEnumLike(ClassElement element, bool expected) {
+ expect((element as ClassElementImpl).isEnumLike, expected);
+ }
}
extension on ClassElement {
diff --git a/pkg/analyzer/test/verify_diagnostics_test.dart b/pkg/analyzer/test/verify_diagnostics_test.dart
index 4b57873..d8d7076 100644
--- a/pkg/analyzer/test/verify_diagnostics_test.dart
+++ b/pkg/analyzer/test/verify_diagnostics_test.dart
@@ -420,6 +420,7 @@
/// codes.
@reflectiveTest
class VerifyDiagnosticsTest {
+ @TestTimeout(Timeout.factor(4))
test_diagnostics() async {
Context pathContext = PhysicalResourceProvider.INSTANCE.pathContext;
List<CodePath> codePaths = computeCodePaths();
diff --git a/pkg/compiler/lib/src/common_elements.dart b/pkg/compiler/lib/src/common_elements.dart
index c8cd6d7..8fdefc4 100644
--- a/pkg/compiler/lib/src/common_elements.dart
+++ b/pkg/compiler/lib/src/common_elements.dart
@@ -217,7 +217,7 @@
InterfaceType getConstantListTypeFor(InterfaceType sourceType);
InterfaceType getConstantMapTypeFor(InterfaceType sourceType,
- {bool hasProtoKey: false, bool onlyStringKeys: false});
+ {bool onlyStringKeys: false});
InterfaceType getConstantSetTypeFor(InterfaceType sourceType);
@@ -362,7 +362,6 @@
ClassEntity get constantMapClass;
ClassEntity get constantStringMapClass;
- ClassEntity get constantProtoMapClass;
ClassEntity get generalConstantMapClass;
ClassEntity get annotationCreatesClass;
@@ -1037,10 +1036,9 @@
@override
InterfaceType getConstantMapTypeFor(InterfaceType sourceType,
- {bool hasProtoKey: false, bool onlyStringKeys: false}) {
- ClassEntity classElement = onlyStringKeys
- ? (hasProtoKey ? constantProtoMapClass : constantStringMapClass)
- : generalConstantMapClass;
+ {bool onlyStringKeys: false}) {
+ ClassEntity classElement =
+ onlyStringKeys ? constantStringMapClass : generalConstantMapClass;
if (dartTypes.treatAsRawType(sourceType)) {
return _env.getRawType(classElement);
} else {
@@ -1619,9 +1617,6 @@
ClassEntity get constantStringMapClass =>
_findHelperClass(constant_system.JavaScriptMapConstant.DART_STRING_CLASS);
@override
- ClassEntity get constantProtoMapClass =>
- _findHelperClass(constant_system.JavaScriptMapConstant.DART_PROTO_CLASS);
- @override
ClassEntity get generalConstantMapClass => _findHelperClass(
constant_system.JavaScriptMapConstant.DART_GENERAL_CLASS);
diff --git a/pkg/compiler/lib/src/constants/constant_system.dart b/pkg/compiler/lib/src/constants/constant_system.dart
index b10c628..d2d5b8f 100644
--- a/pkg/compiler/lib/src/constants/constant_system.dart
+++ b/pkg/compiler/lib/src/constants/constant_system.dart
@@ -157,23 +157,10 @@
InterfaceType sourceType,
List<ConstantValue> keys,
List<ConstantValue> values) {
- bool onlyStringKeys = true;
- ConstantValue protoValue = null;
- for (int i = 0; i < keys.length; i++) {
- dynamic key = keys[i];
- if (key.isString) {
- if (key.stringValue == JavaScriptMapConstant.PROTO_PROPERTY) {
- protoValue = values[i];
- }
- } else {
- onlyStringKeys = false;
- // Don't handle __proto__ values specially in the general map case.
- protoValue = null;
- break;
- }
- }
+ bool onlyStringKeys = keys.every((key) =>
+ key is StringConstantValue &&
+ key.stringValue != JavaScriptMapConstant.PROTO_PROPERTY);
- bool hasProtoKey = (protoValue != null);
InterfaceType keysType;
if (commonElements.dartTypes.treatAsRawType(sourceType)) {
keysType = commonElements.listType();
@@ -182,9 +169,8 @@
}
ListConstantValue keysList = createList(commonElements, keysType, keys);
InterfaceType type = commonElements.getConstantMapTypeFor(sourceType,
- hasProtoKey: hasProtoKey, onlyStringKeys: onlyStringKeys);
- return new JavaScriptMapConstant(
- type, keysList, values, protoValue, onlyStringKeys);
+ onlyStringKeys: onlyStringKeys);
+ return JavaScriptMapConstant(type, keysList, values, onlyStringKeys);
}
ConstantValue createSymbol(CommonElements commonElements, String text) {
@@ -1035,20 +1021,17 @@
/// The dart class implementing constant map literals.
static const String DART_CLASS = "ConstantMap";
static const String DART_STRING_CLASS = "ConstantStringMap";
- static const String DART_PROTO_CLASS = "ConstantProtoMap";
static const String DART_GENERAL_CLASS = "GeneralConstantMap";
static const String LENGTH_NAME = "_length";
static const String JS_OBJECT_NAME = "_jsObject";
static const String KEYS_NAME = "_keys";
- static const String PROTO_VALUE = "_protoValue";
static const String JS_DATA_NAME = "_jsData";
final ListConstantValue keyList;
- final ConstantValue protoValue;
final bool onlyStringKeys;
JavaScriptMapConstant(InterfaceType type, ListConstantValue keyList,
- List<ConstantValue> values, this.protoValue, this.onlyStringKeys)
+ List<ConstantValue> values, this.onlyStringKeys)
: this.keyList = keyList,
super(type, keyList.entries, values);
@override
diff --git a/pkg/compiler/lib/src/constants/values.dart b/pkg/compiler/lib/src/constants/values.dart
index f33c13b..86636d0 100644
--- a/pkg/compiler/lib/src/constants/values.dart
+++ b/pkg/compiler/lib/src/constants/values.dart
@@ -741,8 +741,7 @@
int get length => keys.length;
ConstantValue lookup(ConstantValue key) {
- var lookupMap = _lookupMap ??=
- new Map<ConstantValue, ConstantValue>.fromIterables(keys, values);
+ var lookupMap = _lookupMap ??= Map.fromIterables(keys, values);
return lookupMap[key];
}
diff --git a/pkg/compiler/lib/src/js_backend/backend_impact.dart b/pkg/compiler/lib/src/js_backend/backend_impact.dart
index d50ddc1b..4dcff21 100644
--- a/pkg/compiler/lib/src/js_backend/backend_impact.dart
+++ b/pkg/compiler/lib/src/js_backend/backend_impact.dart
@@ -284,7 +284,6 @@
BackendImpact get constantMapLiteral {
return _constantMapLiteral ??= new BackendImpact(instantiatedClasses: [
_commonElements.constantMapClass,
- _commonElements.constantProtoMapClass,
_commonElements.constantStringMapClass,
_commonElements.generalConstantMapClass,
]);
diff --git a/pkg/compiler/lib/src/js_backend/constant_emitter.dart b/pkg/compiler/lib/src/js_backend/constant_emitter.dart
index dd67c4a..b05bc22 100644
--- a/pkg/compiler/lib/src/js_backend/constant_emitter.dart
+++ b/pkg/compiler/lib/src/js_backend/constant_emitter.dart
@@ -330,10 +330,6 @@
constant_system.JavaScriptMapConstant.KEYS_NAME) {
arguments.add(_constantReferenceGenerator(constant.keyList));
} else if (field.name ==
- constant_system.JavaScriptMapConstant.PROTO_VALUE) {
- assert(constant.protoValue != null);
- arguments.add(_constantReferenceGenerator(constant.protoValue));
- } else if (field.name ==
constant_system.JavaScriptMapConstant.JS_DATA_NAME) {
arguments.add(jsGeneralMap());
} else {
@@ -344,8 +340,6 @@
});
if ((className == constant_system.JavaScriptMapConstant.DART_STRING_CLASS &&
emittedArgumentCount != 3) ||
- (className == constant_system.JavaScriptMapConstant.DART_PROTO_CLASS &&
- emittedArgumentCount != 4) ||
(className ==
constant_system.JavaScriptMapConstant.DART_GENERAL_CLASS &&
emittedArgumentCount != 1)) {
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 8a4928d..f125f55 100644
--- a/pkg/compiler/lib/src/js_model/js_world_builder.dart
+++ b/pkg/compiler/lib/src/js_model/js_world_builder.dart
@@ -944,15 +944,13 @@
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 &&
- protoValue == constant.protoValue) {
+ type == constant.type) {
return constant;
}
return new constant_system.JavaScriptMapConstant(
- type, keys, values, protoValue, constant.onlyStringKeys);
+ type, keys, values, constant.onlyStringKeys);
}
@override
diff --git a/pkg/compiler/lib/src/serialization/abstract_sink.dart b/pkg/compiler/lib/src/serialization/abstract_sink.dart
index 1bd824c..d4a4746 100644
--- a/pkg/compiler/lib/src/serialization/abstract_sink.dart
+++ b/pkg/compiler/lib/src/serialization/abstract_sink.dart
@@ -506,7 +506,6 @@
writeDartType(constant.type);
writeConstant(constant.keyList);
writeConstants(constant.values);
- writeConstantOrNull(constant.protoValue);
writeBool(constant.onlyStringKeys);
break;
case ConstantValueKind.CONSTRUCTED:
diff --git a/pkg/compiler/lib/src/serialization/abstract_source.dart b/pkg/compiler/lib/src/serialization/abstract_source.dart
index a06007f..ed14321 100644
--- a/pkg/compiler/lib/src/serialization/abstract_source.dart
+++ b/pkg/compiler/lib/src/serialization/abstract_source.dart
@@ -519,10 +519,9 @@
DartType type = readDartType();
ListConstantValue keyList = readConstant();
List<ConstantValue> values = readConstants();
- ConstantValue protoValue = readConstantOrNull();
bool onlyStringKeys = readBool();
return new constant_system.JavaScriptMapConstant(
- type, keyList, values, protoValue, onlyStringKeys);
+ type, keyList, values, onlyStringKeys);
case ConstantValueKind.CONSTRUCTED:
InterfaceType type = readDartType();
Map<FieldEntity, ConstantValue> fields =
diff --git a/pkg/compiler/test/impact/data/constants/main.dart b/pkg/compiler/test/impact/data/constants/main.dart
index a29bd63..be9a623 100644
--- a/pkg/compiler/test/impact/data/constants/main.dart
+++ b/pkg/compiler/test/impact/data/constants/main.dart
@@ -108,13 +108,27 @@
inst:List<bool*>]*/
listLiteral() => const [true, false];
-/*member: mapLiteral:type=[inst:ConstantMap<dynamic,dynamic>,inst:ConstantProtoMap<dynamic,dynamic>,inst:ConstantStringMap<dynamic,dynamic>,inst:GeneralConstantMap<dynamic,dynamic>,inst:JSBool]*/
+/*member: mapLiteral:type=[
+ inst:ConstantMap<dynamic,dynamic>,
+ inst:ConstantStringMap<dynamic,dynamic>,
+ inst:GeneralConstantMap<dynamic,dynamic>,
+ inst:JSBool]*/
mapLiteral() => const {true: false};
-/*member: stringMapLiteral:type=[inst:ConstantMap<dynamic,dynamic>,inst:ConstantProtoMap<dynamic,dynamic>,inst:ConstantStringMap<dynamic,dynamic>,inst:GeneralConstantMap<dynamic,dynamic>,inst:JSBool,inst:JSString]*/
+/*member: stringMapLiteral:type=[
+ inst:ConstantMap<dynamic,dynamic>,
+ inst:ConstantStringMap<dynamic,dynamic>,
+ inst:GeneralConstantMap<dynamic,dynamic>,
+ inst:JSBool,
+ inst:JSString]*/
stringMapLiteral() => const {'foo': false};
-/*member: setLiteral:type=[inst:ConstantMap<dynamic,dynamic>,inst:ConstantProtoMap<dynamic,dynamic>,inst:ConstantStringMap<dynamic,dynamic>,inst:GeneralConstantMap<dynamic,dynamic>,inst:JSBool,inst:_UnmodifiableSet<dynamic>]*/
+/*member: setLiteral:type=[
+ inst:ConstantMap<dynamic,dynamic>,
+ inst:ConstantStringMap<dynamic,dynamic>,
+ inst:GeneralConstantMap<dynamic,dynamic>,
+ inst:JSBool,
+ inst:_UnmodifiableSet<dynamic>]*/
setLiteral() => const {true, false};
/*member: instanceConstant:
@@ -190,13 +204,27 @@
inst:List<bool*>]*/
listLiteralRef() => listLiteralField;
-/*member: mapLiteralRef:type=[inst:ConstantMap<dynamic,dynamic>,inst:ConstantProtoMap<dynamic,dynamic>,inst:ConstantStringMap<dynamic,dynamic>,inst:GeneralConstantMap<dynamic,dynamic>,inst:JSBool]*/
+/*member: mapLiteralRef:type=[
+ inst:ConstantMap<dynamic,dynamic>,
+ inst:ConstantStringMap<dynamic,dynamic>,
+ inst:GeneralConstantMap<dynamic,dynamic>,
+ inst:JSBool]*/
mapLiteralRef() => mapLiteralField;
-/*member: stringMapLiteralRef:type=[inst:ConstantMap<dynamic,dynamic>,inst:ConstantProtoMap<dynamic,dynamic>,inst:ConstantStringMap<dynamic,dynamic>,inst:GeneralConstantMap<dynamic,dynamic>,inst:JSBool,inst:JSString]*/
+/*member: stringMapLiteralRef:type=[
+ inst:ConstantMap<dynamic,dynamic>,
+ inst:ConstantStringMap<dynamic,dynamic>,
+ inst:GeneralConstantMap<dynamic,dynamic>,
+ inst:JSBool,
+ inst:JSString]*/
stringMapLiteralRef() => stringMapLiteralField;
-/*member: setLiteralRef:type=[inst:ConstantMap<dynamic,dynamic>,inst:ConstantProtoMap<dynamic,dynamic>,inst:ConstantStringMap<dynamic,dynamic>,inst:GeneralConstantMap<dynamic,dynamic>,inst:JSBool,inst:_UnmodifiableSet<dynamic>]*/
+/*member: setLiteralRef:type=[
+ inst:ConstantMap<dynamic,dynamic>,
+ inst:ConstantStringMap<dynamic,dynamic>,
+ inst:GeneralConstantMap<dynamic,dynamic>,
+ inst:JSBool,
+ inst:_UnmodifiableSet<dynamic>]*/
setLiteralRef() => setLiteralField;
/*member: instanceConstantRef:
@@ -263,15 +291,29 @@
listLiteralDeferred() => defer.listLiteralField;
// TODO(johnniwinther): Should we record that this is deferred?
-/*member: mapLiteralDeferred:type=[inst:ConstantMap<dynamic,dynamic>,inst:ConstantProtoMap<dynamic,dynamic>,inst:ConstantStringMap<dynamic,dynamic>,inst:GeneralConstantMap<dynamic,dynamic>,inst:JSBool]*/
+/*member: mapLiteralDeferred:type=[
+ inst:ConstantMap<dynamic,dynamic>,
+ inst:ConstantStringMap<dynamic,dynamic>,
+ inst:GeneralConstantMap<dynamic,dynamic>,
+ inst:JSBool]*/
mapLiteralDeferred() => defer.mapLiteralField;
// TODO(johnniwinther): Should we record that this is deferred?
-/*member: stringMapLiteralDeferred:type=[inst:ConstantMap<dynamic,dynamic>,inst:ConstantProtoMap<dynamic,dynamic>,inst:ConstantStringMap<dynamic,dynamic>,inst:GeneralConstantMap<dynamic,dynamic>,inst:JSBool,inst:JSString]*/
+/*member: stringMapLiteralDeferred:type=[
+ inst:ConstantMap<dynamic,dynamic>,
+ inst:ConstantStringMap<dynamic,dynamic>,
+ inst:GeneralConstantMap<dynamic,dynamic>,
+ inst:JSBool,
+ inst:JSString]*/
stringMapLiteralDeferred() => defer.stringMapLiteralField;
// TODO(johnniwinther): Should we record that this is deferred?
-/*member: setLiteralDeferred:type=[inst:ConstantMap<dynamic,dynamic>,inst:ConstantProtoMap<dynamic,dynamic>,inst:ConstantStringMap<dynamic,dynamic>,inst:GeneralConstantMap<dynamic,dynamic>,inst:JSBool,inst:_UnmodifiableSet<dynamic>]*/
+/*member: setLiteralDeferred:type=[
+ inst:ConstantMap<dynamic,dynamic>,
+ inst:ConstantStringMap<dynamic,dynamic>,
+ inst:GeneralConstantMap<dynamic,dynamic>,
+ inst:JSBool,
+ inst:_UnmodifiableSet<dynamic>]*/
setLiteralDeferred() => defer.setLiteralField;
/*member: instanceConstantDeferred:
diff --git a/pkg/compiler/test/impact/data/literals.dart b/pkg/compiler/test/impact/data/literals.dart
index 05b7c0c..e0e3ba5 100644
--- a/pkg/compiler/test/impact/data/literals.dart
+++ b/pkg/compiler/test/impact/data/literals.dart
@@ -179,7 +179,6 @@
/*member: testEmptyMapLiteralConstant:
type=[
inst:ConstantMap<dynamic,dynamic>,
- inst:ConstantProtoMap<dynamic,dynamic>,
inst:ConstantStringMap<dynamic,dynamic>,
inst:GeneralConstantMap<dynamic,dynamic>]
*/
diff --git a/pkg/pkg.status b/pkg/pkg.status
index 79e9538..8386f5a 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -66,6 +66,7 @@
kernel/testcases/*: Skip # These are not tests but input for tests.
vm/test/transformations/type_flow/transformer_test: Slow, Pass
vm/testcases/*: SkipByDesign # These are not tests but input for tests.
+vm_service/test/cpu_samples_stream_test: Slow, Pass # Requires CPU sample buffer to fill.
wasm/*: SkipByDesign # These can't be run without running wasm:setup first.
[ $compiler == dart2analyzer ]
diff --git a/pkg/vm_service/example/vm_service_assert.dart b/pkg/vm_service/example/vm_service_assert.dart
index b458ad6..8914a67 100644
--- a/pkg/vm_service/example/vm_service_assert.dart
+++ b/pkg/vm_service/example/vm_service_assert.dart
@@ -137,6 +137,7 @@
if (obj == "BreakpointRemoved") return obj;
if (obj == "BreakpointResolved") return obj;
if (obj == "BreakpointUpdated") return obj;
+ if (obj == "CpuSamples") return obj;
if (obj == "Extension") return obj;
if (obj == "GC") return obj;
if (obj == "Inspect") return obj;
diff --git a/pkg/vm_service/java/version.properties b/pkg/vm_service/java/version.properties
index 62cbe79..5e787ed 100644
--- a/pkg/vm_service/java/version.properties
+++ b/pkg/vm_service/java/version.properties
@@ -1 +1 @@
-version=3.48
+version=3.49
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index 70c6db8..c80ebed 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -26,7 +26,7 @@
HeapSnapshotObjectNoData,
HeapSnapshotObjectNullData;
-const String vmServiceVersion = '3.48.0';
+const String vmServiceVersion = '3.49.0';
/// @optional
const String optional = 'optional';
@@ -1149,7 +1149,7 @@
/// Debug | PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted,
/// PauseException, PausePostRequest, Resume, BreakpointAdded,
/// BreakpointResolved, BreakpointRemoved, BreakpointUpdated, Inspect, None
- /// Profiler | UserTagChanged
+ /// Profiler | CpuSamples, UserTagChanged
/// GC | GC
/// Extension | Extension
/// Timeline | TimelineEvents, TimelineStreamsSubscriptionUpdate
@@ -1690,7 +1690,7 @@
// PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted, PauseException, PausePostRequest, Resume, BreakpointAdded, BreakpointResolved, BreakpointRemoved, BreakpointUpdated, Inspect, None
Stream<Event> get onDebugEvent => _getEventController('Debug').stream;
- // UserTagChanged
+ // CpuSamples, UserTagChanged
Stream<Event> get onProfilerEvent => _getEventController('Profiler').stream;
// GC
@@ -2502,6 +2502,9 @@
/// Notification that the UserTag for an isolate has been changed.
static const String kUserTagChanged = 'UserTagChanged';
+
+ /// A block of recently collected CPU samples.
+ static const String kCpuSamples = 'CpuSamples';
}
/// Adding new values to `InstanceKind` is considered a backwards compatible
@@ -3899,6 +3902,10 @@
@optional
String? previousTag;
+ /// A CPU profile containing recent samples.
+ @optional
+ CpuSamples? cpuSamples;
+
/// Binary data associated with the event.
///
/// This is provided for the event kinds:
@@ -3933,6 +3940,7 @@
this.last,
this.updatedTag,
this.previousTag,
+ this.cpuSamples,
this.data,
});
@@ -3977,6 +3985,8 @@
last = json['last'];
updatedTag = json['updatedTag'];
previousTag = json['previousTag'];
+ cpuSamples = createServiceObject(json['cpuSamples'], const ['CpuSamples'])
+ as CpuSamples?;
data = json['data'];
}
@@ -4018,6 +4028,7 @@
_setIfNotNull(json, 'last', last);
_setIfNotNull(json, 'updatedTag', updatedTag);
_setIfNotNull(json, 'previousTag', previousTag);
+ _setIfNotNull(json, 'cpuSamples', cpuSamples?.toJson());
_setIfNotNull(json, 'data', data);
return json;
}
diff --git a/pkg/vm_service/test/cpu_samples_stream_test.dart b/pkg/vm_service/test/cpu_samples_stream_test.dart
new file mode 100644
index 0000000..d6846ef
--- /dev/null
+++ b/pkg/vm_service/test/cpu_samples_stream_test.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2021, 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 'dart:async';
+
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart';
+
+import 'common/service_test_common.dart';
+import 'common/test_helper.dart';
+
+fib(int n) {
+ if (n <= 1) {
+ return n;
+ }
+ return fib(n - 1) + fib(n - 2);
+}
+
+void testMain() async {
+ int i = 10;
+ while (true) {
+ ++i;
+ // Create progressively deeper stacks to more quickly fill the sample
+ // buffer.
+ fib(i);
+ }
+}
+
+late StreamSubscription sub;
+
+var tests = <IsolateTest>[
+ (VmService service, IsolateRef isolate) async {
+ final completer = Completer<void>();
+ int count = 0;
+ int previousOrigin = 0;
+ sub = service.onProfilerEvent.listen((event) async {
+ count++;
+ expect(event.kind, EventKind.kCpuSamples);
+ expect(event.cpuSamples, isNotNull);
+ expect(event.cpuSamples!.samples!.isNotEmpty, true);
+ if (previousOrigin != 0) {
+ expect(
+ event.cpuSamples!.timeOriginMicros! >= previousOrigin,
+ true,
+ );
+ }
+ previousOrigin = event.cpuSamples!.timeOriginMicros!;
+
+ if (count == 2) {
+ await sub.cancel();
+ completer.complete();
+ }
+ });
+ await service.streamListen(EventStreams.kProfiler);
+
+ await completer.future;
+ await service.streamCancel(EventStreams.kProfiler);
+ },
+];
+
+main([args = const <String>[]]) async => await runIsolateTests(
+ args,
+ tests,
+ 'cpu_samples_stream_test.dart',
+ testeeConcurrent: testMain,
+ extraArgs: [
+ '--sample-buffer-duration=1',
+ '--profile-vm',
+ ],
+ );
diff --git a/runtime/observatory/tests/service/get_version_rpc_test.dart b/runtime/observatory/tests/service/get_version_rpc_test.dart
index 75d1743..f153eac 100644
--- a/runtime/observatory/tests/service/get_version_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_version_rpc_test.dart
@@ -12,7 +12,7 @@
final result = await vm.invokeRpcNoUpgrade('getVersion', {});
expect(result['type'], 'Version');
expect(result['major'], 3);
- expect(result['minor'], 48);
+ expect(result['minor'], 49);
expect(result['_privateMajor'], 0);
expect(result['_privateMinor'], 0);
},
diff --git a/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart b/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart
index 443f8bb..a36deeb 100644
--- a/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart
+++ b/runtime/observatory_2/tests/service_2/get_version_rpc_test.dart
@@ -12,7 +12,7 @@
final result = await vm.invokeRpcNoUpgrade('getVersion', {});
expect(result['type'], equals('Version'));
expect(result['major'], equals(3));
- expect(result['minor'], equals(48));
+ expect(result['minor'], equals(49));
expect(result['_privateMajor'], equals(0));
expect(result['_privateMinor'], equals(0));
},
diff --git a/runtime/platform/atomic.h b/runtime/platform/atomic.h
index ffb5bdc..390aa62 100644
--- a/runtime/platform/atomic.h
+++ b/runtime/platform/atomic.h
@@ -45,6 +45,10 @@
return value_.fetch_and(arg, order);
}
+ T exchange(T arg, std::memory_order order = std::memory_order_relaxed) {
+ return value_.exchange(arg, order);
+ }
+
bool compare_exchange_weak(
T& expected, // NOLINT
T desired,
@@ -76,6 +80,10 @@
}
T operator+=(T arg) { return fetch_add(arg) + arg; }
T operator-=(T arg) { return fetch_sub(arg) - arg; }
+ T& operator++() { return fetch_add(1) + 1; }
+ T& operator--() { return fetch_sub(1) - 1; }
+ T operator++(int) { return fetch_add(1); }
+ T operator--(int) { return fetch_sub(1); }
private:
std::atomic<T> value_;
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index dd85b96..dffed84 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -2383,6 +2383,25 @@
reinterpret_cast<uword>(this));
}
+#if !defined(PRODUCT)
+void Isolate::set_current_sample_block(SampleBlock* current) {
+ ASSERT(current_sample_block_lock_.IsOwnedByCurrentThread());
+ if (current != nullptr) {
+ current->set_is_allocation_block(false);
+ current->set_owner(this);
+ }
+ current_sample_block_ = current;
+}
+
+void Isolate::set_current_allocation_sample_block(SampleBlock* current) {
+ if (current != nullptr) {
+ current->set_is_allocation_block(true);
+ current->set_owner(this);
+ }
+ current_allocation_sample_block_ = current;
+}
+#endif // !defined(PRODUCT)
+
// static
void Isolate::NotifyLowMemory() {
Isolate::KillAllIsolates(Isolate::kLowMemoryMsg);
@@ -2516,11 +2535,12 @@
}
void Isolate::LowLevelCleanup(Isolate* isolate) {
-#if !defined(DART_PECOMPILED_RUNTIME)
+#if !defined(DART_PRECOMPILED_RUNTIME)
if (KernelIsolate::IsKernelIsolate(isolate)) {
KernelIsolate::SetKernelIsolate(nullptr);
+ }
#endif
- } else if (ServiceIsolate::IsServiceIsolate(isolate)) {
+ if (ServiceIsolate::IsServiceIsolate(isolate)) {
ServiceIsolate::SetServiceIsolate(nullptr);
}
@@ -2538,6 +2558,26 @@
// requests anymore.
Thread::ExitIsolate();
+#if !defined(PRODUCT)
+ // Cleanup profiler state.
+ SampleBlock* cpu_block = isolate->current_sample_block();
+ if (cpu_block != nullptr) {
+ cpu_block->release_block();
+ }
+ SampleBlock* allocation_block = isolate->current_allocation_sample_block();
+ if (allocation_block != nullptr) {
+ allocation_block->release_block();
+ }
+
+ // Process the previously assigned sample blocks if we're using the
+ // profiler's sample buffer. Some tests create their own SampleBlockBuffer
+ // and handle block processing themselves.
+ if ((cpu_block != nullptr || allocation_block != nullptr) &&
+ Profiler::sample_block_buffer() != nullptr) {
+ Profiler::sample_block_buffer()->ProcessCompletedBlocks();
+ }
+#endif // !defined(PRODUCT)
+
// Now it's safe to delete the isolate.
delete isolate;
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index 09dbbcd..261b14f 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -74,6 +74,8 @@
class SafepointRwLock;
class SafepointHandler;
class SampleBuffer;
+class SampleBlock;
+class SampleBlockBuffer;
class SendPort;
class SerializedObjectBuffer;
class ServiceIdZone;
@@ -1084,6 +1086,27 @@
#if !defined(PRODUCT)
Debugger* debugger() const { return debugger_; }
+ // NOTE: this lock should only be acquired within the profiler signal handler.
+ Mutex* current_sample_block_lock() const {
+ return const_cast<Mutex*>(¤t_sample_block_lock_);
+ }
+
+ // Returns the current SampleBlock used to track CPU profiling samples.
+ //
+ // NOTE: current_sample_block_lock() should be held when accessing this
+ // block.
+ SampleBlock* current_sample_block() const { return current_sample_block_; }
+ void set_current_sample_block(SampleBlock* current);
+
+ // Returns the current SampleBlock used to track Dart allocation samples.
+ //
+ // Allocations should only occur on the mutator thread for an isolate, so we
+ // don't need to worry about grabbing a lock while accessing this block.
+ SampleBlock* current_allocation_sample_block() const {
+ return current_allocation_sample_block_;
+ }
+ void set_current_allocation_sample_block(SampleBlock* current);
+
void set_single_step(bool value) { single_step_ = value; }
bool single_step() const { return single_step_; }
static intptr_t single_step_offset() {
@@ -1528,6 +1551,21 @@
// the top.
#if !defined(PRODUCT)
Debugger* debugger_ = nullptr;
+
+ // SampleBlock containing CPU profiling samples.
+ //
+ // Can be accessed by multiple threads, so current_sample_block_lock_ should
+ // be acquired before accessing.
+ SampleBlock* current_sample_block_ = nullptr;
+ Mutex current_sample_block_lock_;
+
+ // SampleBlock containing Dart allocation profiling samples.
+ //
+ // Allocations should only occur on the mutator thread for an isolate, so we
+ // shouldn't need to worry about grabbing a lock for the allocation sample
+ // block.
+ SampleBlock* current_allocation_sample_block_ = nullptr;
+
int64_t last_resume_timestamp_;
VMTagCounters vm_tag_counters_;
diff --git a/runtime/vm/malloc_hooks_test.cc b/runtime/vm/malloc_hooks_test.cc
index 8edd992..d04ab64 100644
--- a/runtime/vm/malloc_hooks_test.cc
+++ b/runtime/vm/malloc_hooks_test.cc
@@ -176,7 +176,7 @@
EnableMallocHooksAndStacksScope scope;
ClearProfileVisitor cpv(Isolate::Current());
- Profiler::sample_buffer()->VisitSamples(&cpv);
+ Profiler::sample_block_buffer()->VisitSamples(&cpv);
char* var = static_cast<char*>(malloc(16 * sizeof(char)));
JSONStream js;
diff --git a/runtime/vm/profiler.cc b/runtime/vm/profiler.cc
index 532eb63..4590a76 100644
--- a/runtime/vm/profiler.cc
+++ b/runtime/vm/profiler.cc
@@ -19,6 +19,7 @@
#include "vm/object.h"
#include "vm/os.h"
#include "vm/profiler.h"
+#include "vm/profiler_service.h"
#include "vm/reusable_handles.h"
#include "vm/signal_handler.h"
#include "vm/simulator.h"
@@ -61,8 +62,8 @@
#ifndef PRODUCT
RelaxedAtomic<bool> Profiler::initialized_ = false;
-SampleBuffer* Profiler::sample_buffer_ = NULL;
-AllocationSampleBuffer* Profiler::allocation_sample_buffer_ = NULL;
+SampleBlockBuffer* Profiler::sample_block_buffer_ = nullptr;
+AllocationSampleBuffer* Profiler::allocation_sample_buffer_ = nullptr;
ProfilerCounters Profiler::counters_ = {};
void Profiler::Init() {
@@ -75,9 +76,9 @@
SetSamplePeriod(FLAG_profile_period);
// The profiler may have been shutdown previously, in which case the sample
// buffer will have already been initialized.
- if (sample_buffer_ == NULL) {
- intptr_t capacity = CalculateSampleBufferCapacity();
- sample_buffer_ = new SampleBuffer(capacity);
+ if (sample_block_buffer_ == nullptr) {
+ intptr_t num_blocks = CalculateSampleBufferCapacity();
+ sample_block_buffer_ = new SampleBlockBuffer(num_blocks);
Profiler::InitAllocationSampleBuffer();
}
ThreadInterrupter::Init();
@@ -92,14 +93,30 @@
}
}
+class SampleBlockCleanupVisitor : public IsolateVisitor {
+ public:
+ SampleBlockCleanupVisitor() = default;
+ virtual ~SampleBlockCleanupVisitor() = default;
+
+ void VisitIsolate(Isolate* isolate) {
+ isolate->set_current_allocation_sample_block(nullptr);
+ {
+ MutexLocker ml(isolate->current_sample_block_lock());
+ isolate->set_current_sample_block(nullptr);
+ }
+ }
+};
+
void Profiler::Cleanup() {
if (!FLAG_profiler) {
return;
}
ASSERT(initialized_);
ThreadInterrupter::Cleanup();
- delete sample_buffer_;
- sample_buffer_ = NULL;
+ SampleBlockCleanupVisitor visitor;
+ Isolate::VisitIsolates(&visitor);
+ delete sample_block_buffer_;
+ sample_block_buffer_ = nullptr;
initialized_ = false;
}
@@ -130,16 +147,16 @@
intptr_t Profiler::CalculateSampleBufferCapacity() {
if (FLAG_sample_buffer_duration <= 0) {
- return SampleBuffer::kDefaultBufferCapacity;
+ return SampleBlockBuffer::kDefaultBlockCount;
}
// Deeper stacks require more than a single Sample object to be represented
// correctly. These samples are chained, so we need to determine the worst
// case sample chain length for a single stack.
const intptr_t max_sample_chain_length =
FLAG_max_profile_depth / kMaxSamplesPerTick;
- const intptr_t buffer_size = FLAG_sample_buffer_duration *
- SamplesPerSecond() * max_sample_chain_length;
- return buffer_size;
+ const intptr_t sample_count = FLAG_sample_buffer_duration *
+ SamplesPerSecond() * max_sample_chain_length;
+ return (sample_count / SampleBlock::kSamplesPerBlock) + 1;
}
void Profiler::SetSamplePeriod(intptr_t period) {
@@ -156,7 +173,166 @@
SetSamplePeriod(FLAG_profile_period);
}
-SampleBuffer::SampleBuffer(intptr_t capacity) {
+SampleBlockBuffer::SampleBlockBuffer(intptr_t blocks,
+ intptr_t samples_per_block) {
+ const intptr_t size = Utils::RoundUp(
+ blocks * samples_per_block * sizeof(Sample), VirtualMemory::PageSize());
+ const bool kNotExecutable = false;
+ memory_ = VirtualMemory::Allocate(size, kNotExecutable, "dart-profiler");
+ if (memory_ == NULL) {
+ OUT_OF_MEMORY();
+ }
+ sample_buffer_ = reinterpret_cast<Sample*>(memory_->address());
+ blocks_ = new SampleBlock[blocks];
+ for (intptr_t i = 0; i < blocks; ++i) {
+ blocks_[i].Init(&sample_buffer_[i * samples_per_block], samples_per_block);
+ }
+ capacity_ = blocks;
+ cursor_ = 0;
+ free_list_head_ = nullptr;
+ free_list_tail_ = nullptr;
+}
+
+SampleBlockBuffer::~SampleBlockBuffer() {
+ delete[] blocks_;
+ blocks_ = nullptr;
+ capacity_ = 0;
+ cursor_ = 0;
+}
+
+SampleBlock* SampleBlockBuffer::ReserveSampleBlock() {
+ // Don't increment right away to avoid unlikely wrap-around errors.
+ if (cursor_.load() < capacity_) {
+ intptr_t index = cursor_.fetch_add(1u);
+ // Check the index again to make sure the last block hasn't been snatched
+ // from underneath us.
+ if (index < capacity_) {
+ return &blocks_[index];
+ }
+ }
+ // Try to re-use a previously freed SampleBlock once we've handed out each
+ // block at least once. Freed blocks aren't cleared immediately and are still
+ // valid until they're re-allocated, similar to how a ring buffer would clear
+ // the oldest samples.
+ SampleBlock* block = GetFreeBlock();
+ if (block != nullptr) {
+ block->Clear();
+ }
+ return block;
+}
+
+void SampleBlockBuffer::ProcessCompletedBlocks() {
+ Thread* thread = Thread::Current();
+ int64_t start = Dart_TimelineGetMicros();
+ for (intptr_t i = 0; i < capacity_; ++i) {
+ SampleBlock* block = &blocks_[i];
+ if (block->is_full() && !block->evictable()) {
+ if (Service::profiler_stream.enabled()) {
+ Profile profile(block->owner());
+ profile.Build(thread, nullptr, block);
+ ServiceEvent event(block->owner(), ServiceEvent::kCpuSamples);
+ event.set_cpu_profile(&profile);
+ Service::HandleEvent(&event);
+ }
+ block->evictable_ = true;
+ FreeBlock(block);
+ }
+ }
+ int64_t end = Dart_TimelineGetMicros();
+ Dart_TimelineEvent("SampleBlockBuffer::ProcessCompletedBlocks", start, end,
+ Dart_Timeline_Event_Duration, 0, nullptr, nullptr);
+}
+
+ProcessedSampleBuffer* SampleBlockBuffer::BuildProcessedSampleBuffer(
+ SampleFilter* filter,
+ ProcessedSampleBuffer* buffer) {
+ ASSERT(filter != NULL);
+ Thread* thread = Thread::Current();
+ Zone* zone = thread->zone();
+
+ if (buffer == nullptr) {
+ buffer = new (zone) ProcessedSampleBuffer();
+ }
+
+ for (intptr_t i = 0; i < capacity_; ++i) {
+ (&blocks_[i])->BuildProcessedSampleBuffer(filter, buffer);
+ }
+
+ return buffer;
+}
+
+Sample* SampleBlock::ReserveSample() {
+ if (full_.load()) {
+ return nullptr;
+ }
+ intptr_t slot = cursor_.fetch_add(1u);
+ if (slot + 1 == capacity_) {
+ full_ = true;
+ }
+ return (slot < capacity_) ? At(slot) : nullptr;
+}
+
+Sample* SampleBlock::ReserveSampleAndLink(Sample* previous) {
+ ASSERT(previous != nullptr);
+ SampleBlockBuffer* buffer = Profiler::sample_block_buffer();
+ Isolate* isolate = owner_;
+ ASSERT(isolate != nullptr);
+ Sample* next = previous->is_allocation_sample()
+ ? buffer->ReserveAllocationSample(isolate)
+ : buffer->ReserveCPUSample(isolate);
+ next->Init(previous->port(), previous->timestamp(), previous->tid());
+ next->set_head_sample(false);
+ // Mark that previous continues at next.
+ previous->SetContinuation(next);
+ return next;
+}
+
+Sample* SampleBlockBuffer::ReserveCPUSample(Isolate* isolate) {
+ return ReserveSampleImpl(isolate, false);
+}
+
+Sample* SampleBlockBuffer::ReserveAllocationSample(Isolate* isolate) {
+ return ReserveSampleImpl(isolate, true);
+}
+
+Sample* SampleBlockBuffer::ReserveSampleImpl(Isolate* isolate,
+ bool allocation_sample) {
+ SampleBlock* block = allocation_sample
+ ? isolate->current_allocation_sample_block()
+ : isolate->current_sample_block();
+ Sample* sample = nullptr;
+ if (block != nullptr) {
+ sample = block->ReserveSample();
+ }
+ if (sample != nullptr) {
+ return sample;
+ }
+ SampleBlock* next = nullptr;
+ if (allocation_sample) {
+ // We only need to be locked while accessing the CPU sample block since
+ // Dart allocations can only occur on the mutator thread.
+ next = ReserveSampleBlock();
+ if (next == nullptr) {
+ // We're out of blocks to reserve. Drop the sample.
+ return nullptr;
+ }
+ isolate->set_current_allocation_sample_block(next);
+ } else {
+ MutexLocker locker(isolate->current_sample_block_lock());
+ next = ReserveSampleBlock();
+ if (next == nullptr) {
+ // We're out of blocks to reserve. Drop the sample.
+ return nullptr;
+ }
+ isolate->set_current_sample_block(next);
+ }
+ next->set_is_allocation_block(allocation_sample);
+ can_process_block_.store(true);
+ isolate->mutator_thread()->ScheduleInterrupts(Thread::kVMInterrupt);
+ return ReserveSampleImpl(isolate, allocation_sample);
+}
+
+AllocationSampleBuffer::AllocationSampleBuffer(intptr_t capacity) {
const intptr_t size =
Utils::RoundUp(capacity * sizeof(Sample), VirtualMemory::PageSize());
const bool kNotExecutable = false;
@@ -164,85 +340,19 @@
if (memory_ == NULL) {
OUT_OF_MEMORY();
}
-
- samples_ = reinterpret_cast<Sample*>(memory_->address());
- capacity_ = capacity;
+ Init(reinterpret_cast<Sample*>(memory_->address()), capacity);
+ free_sample_list_ = nullptr;
cursor_ = 0;
-
- if (FLAG_trace_profiler) {
- OS::PrintErr("Profiler holds %" Pd " samples\n", capacity);
- OS::PrintErr("Profiler sample is %" Pd " bytes\n", sizeof(Sample));
- OS::PrintErr("Profiler memory usage = %" Pd " bytes\n", size);
- }
- if (FLAG_sample_buffer_duration != 0) {
- OS::PrintErr(
- "** WARNING ** Custom sample buffer size provided via "
- "--sample-buffer-duration\n");
- OS::PrintErr(
- "The sample buffer can hold at least %ds worth of "
- "samples with stacks depths of up to %d, collected at "
- "a sample rate of %" Pd "Hz.\n",
- FLAG_sample_buffer_duration, FLAG_max_profile_depth,
- SamplesPerSecond());
- OS::PrintErr("The resulting sample buffer size is %" Pd " bytes.\n", size);
- }
-}
-
-AllocationSampleBuffer::AllocationSampleBuffer(intptr_t capacity)
- : SampleBuffer(capacity), mutex_(), free_sample_list_(NULL) {}
-
-SampleBuffer::~SampleBuffer() {
- delete memory_;
-}
-
-AllocationSampleBuffer::~AllocationSampleBuffer() {
-}
-
-Sample* SampleBuffer::At(intptr_t idx) const {
- ASSERT(idx >= 0);
- ASSERT(idx < capacity_);
- return &samples_[idx];
-}
-
-intptr_t SampleBuffer::ReserveSampleSlot() {
- ASSERT(samples_ != NULL);
- uintptr_t cursor = cursor_.fetch_add(1u);
- // Map back into sample buffer range.
- cursor = cursor % capacity_;
- return cursor;
-}
-
-Sample* SampleBuffer::ReserveSample() {
- return At(ReserveSampleSlot());
-}
-
-Sample* SampleBuffer::ReserveSampleAndLink(Sample* previous) {
- ASSERT(previous != NULL);
- intptr_t next_index = ReserveSampleSlot();
- Sample* next = At(next_index);
- next->Init(previous->port(), previous->timestamp(), previous->tid());
- next->set_head_sample(false);
- // Mark that previous continues at next.
- previous->SetContinuationIndex(next_index);
- return next;
}
void AllocationSampleBuffer::FreeAllocationSample(Sample* sample) {
MutexLocker ml(&mutex_);
- while (sample != NULL) {
- intptr_t continuation_index = -1;
- if (sample->is_continuation_sample()) {
- continuation_index = sample->continuation_index();
- }
+ while (sample != nullptr) {
+ Sample* next = sample->continuation_sample();
sample->Clear();
sample->set_next_free(free_sample_list_);
free_sample_list_ = sample;
-
- if (continuation_index != -1) {
- sample = At(continuation_index);
- } else {
- sample = NULL;
- }
+ sample = next;
}
}
@@ -255,7 +365,7 @@
uint8_t* free_sample_ptr = reinterpret_cast<uint8_t*>(free_sample);
return static_cast<intptr_t>((free_sample_ptr - samples_array_ptr) /
sizeof(Sample));
- } else if (cursor_ < static_cast<uintptr_t>(capacity_ - 1)) {
+ } else if (cursor_ < static_cast<intptr_t>(capacity_ - 1)) {
return cursor_ += 1;
} else {
return -1;
@@ -277,7 +387,7 @@
previous->native_allocation_size_bytes());
next->set_head_sample(false);
// Mark that previous continues at next.
- previous->SetContinuationIndex(next_index);
+ previous->SetContinuation(next);
return next;
}
@@ -957,12 +1067,16 @@
}
static Sample* SetupSample(Thread* thread,
- SampleBuffer* sample_buffer,
+ bool allocation_sample,
ThreadId tid) {
ASSERT(thread != NULL);
Isolate* isolate = thread->isolate();
- ASSERT(sample_buffer != NULL);
- Sample* sample = sample_buffer->ReserveSample();
+ SampleBlockBuffer* buffer = Profiler::sample_block_buffer();
+ Sample* sample = allocation_sample ? buffer->ReserveAllocationSample(isolate)
+ : buffer->ReserveCPUSample(isolate);
+ if (sample == nullptr) {
+ return nullptr;
+ }
sample->Init(isolate->main_port(), OS::GetCurrentMonotonicMicros(), tid);
uword vm_tag = thread->vm_tag();
#if defined(USING_SIMULATOR)
@@ -980,7 +1094,8 @@
return sample;
}
-static Sample* SetupSampleNative(SampleBuffer* sample_buffer, ThreadId tid) {
+static Sample* SetupSampleNative(AllocationSampleBuffer* sample_buffer,
+ ThreadId tid) {
Sample* sample = sample_buffer->ReserveSample();
if (sample == NULL) {
return NULL;
@@ -1129,11 +1244,10 @@
if (!CheckIsolate(isolate)) {
return;
}
-
const bool exited_dart_code = thread->HasExitedDartCode();
- SampleBuffer* sample_buffer = Profiler::sample_buffer();
- if (sample_buffer == NULL) {
+ SampleBlockBuffer* buffer = Profiler::sample_block_buffer();
+ if (buffer == nullptr) {
// Profiler not initialized.
return;
}
@@ -1157,24 +1271,30 @@
return;
}
- Sample* sample = SetupSample(thread, sample_buffer, os_thread->trace_id());
+ Sample* sample =
+ SetupSample(thread, /*allocation_block*/ true, os_thread->trace_id());
+ if (sample == nullptr) {
+ // We were unable to assign a sample for this allocation.
+ counters_.sample_allocation_failure++;
+ return;
+ }
sample->SetAllocationCid(cid);
sample->set_allocation_identity_hash(identity_hash);
if (FLAG_profile_vm_allocation) {
ProfilerNativeStackWalker native_stack_walker(
&counters_, (isolate != NULL) ? isolate->main_port() : ILLEGAL_PORT,
- sample, sample_buffer, stack_lower, stack_upper, pc, fp, sp);
+ sample, isolate->current_allocation_sample_block(), stack_lower,
+ stack_upper, pc, fp, sp);
native_stack_walker.walk();
} else if (exited_dart_code) {
ProfilerDartStackWalker dart_exit_stack_walker(
- thread, sample, sample_buffer, pc, fp, /* allocation_sample*/ true);
+ thread, sample, isolate->current_allocation_sample_block(), pc, fp,
+ /* allocation_sample*/ true);
dart_exit_stack_walker.walk();
} else {
// Fall back.
uintptr_t pc = OS::GetProgramCounter();
- Sample* sample = SetupSample(thread, sample_buffer, os_thread->trace_id());
- sample->SetAllocationCid(cid);
sample->SetAt(0, pc);
}
}
@@ -1231,20 +1351,16 @@
return sample;
}
-void Profiler::SampleThreadSingleFrame(Thread* thread, uintptr_t pc) {
+void Profiler::SampleThreadSingleFrame(Thread* thread,
+ Sample* sample,
+ uintptr_t pc) {
ASSERT(thread != NULL);
OSThread* os_thread = thread->os_thread();
ASSERT(os_thread != NULL);
Isolate* isolate = thread->isolate();
- SampleBuffer* sample_buffer = Profiler::sample_buffer();
- if (sample_buffer == NULL) {
- // Profiler not initialized.
- return;
- }
+ ASSERT(Profiler::sample_block_buffer() != nullptr);
- // Setup sample.
- Sample* sample = SetupSample(thread, sample_buffer, os_thread->trace_id());
// Increment counter for vm tag.
VMTagCounters* counters = isolate->vm_tag_counters();
ASSERT(counters != NULL);
@@ -1306,22 +1422,37 @@
return;
}
+ SampleBlockBuffer* sample_block_buffer = Profiler::sample_block_buffer();
+ if (sample_block_buffer == nullptr) {
+ // Profiler not initialized.
+ return;
+ }
+
+ // Setup sample.
+ Sample* sample =
+ SetupSample(thread, /*allocation_block*/ false, os_thread->trace_id());
+ if (sample == nullptr) {
+ // We were unable to assign a sample for this profiler tick.
+ counters_.sample_allocation_failure++;
+ return;
+ }
+
if (thread->IsMutatorThread()) {
if (isolate->IsDeoptimizing()) {
counters_.single_frame_sample_deoptimizing.fetch_add(1);
- SampleThreadSingleFrame(thread, pc);
+ SampleThreadSingleFrame(thread, sample, pc);
return;
}
if (isolate->group()->compaction_in_progress()) {
// The Dart stack isn't fully walkable.
- SampleThreadSingleFrame(thread, pc);
+ SampleThreadSingleFrame(thread, sample, pc);
return;
}
}
if (!InitialRegisterCheck(pc, fp, sp)) {
counters_.single_frame_sample_register_check.fetch_add(1);
- SampleThreadSingleFrame(thread, pc);
+ SampleThreadSingleFrame(thread, sample, pc);
return;
}
@@ -1331,20 +1462,13 @@
&stack_upper)) {
counters_.single_frame_sample_get_and_validate_stack_bounds.fetch_add(1);
// Could not get stack boundary.
- SampleThreadSingleFrame(thread, pc);
+ SampleThreadSingleFrame(thread, sample, pc);
return;
}
// At this point we have a valid stack boundary for this isolate and
// know that our initial stack and frame pointers are within the boundary.
- SampleBuffer* sample_buffer = Profiler::sample_buffer();
- if (sample_buffer == NULL) {
- // Profiler not initialized.
- return;
- }
- // Setup sample.
- Sample* sample = SetupSample(thread, sample_buffer, os_thread->trace_id());
// Increment counter for vm tag.
VMTagCounters* counters = isolate->vm_tag_counters();
ASSERT(counters != NULL);
@@ -1354,9 +1478,11 @@
ProfilerNativeStackWalker native_stack_walker(
&counters_, (isolate != NULL) ? isolate->main_port() : ILLEGAL_PORT,
- sample, sample_buffer, stack_lower, stack_upper, pc, fp, sp);
+ sample, isolate->current_sample_block(), stack_lower, stack_upper, pc, fp,
+ sp);
const bool exited_dart_code = thread->HasExitedDartCode();
- ProfilerDartStackWalker dart_stack_walker(thread, sample, sample_buffer, pc,
+ ProfilerDartStackWalker dart_stack_walker(thread, sample,
+ isolate->current_sample_block(), pc,
fp, /* allocation_sample*/ false);
// All memory access is done inside CollectSample.
@@ -1480,12 +1606,14 @@
}
ProcessedSampleBuffer* SampleBuffer::BuildProcessedSampleBuffer(
- SampleFilter* filter) {
- ASSERT(filter != NULL);
+ SampleFilter* filter,
+ ProcessedSampleBuffer* buffer) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
- ProcessedSampleBuffer* buffer = new (zone) ProcessedSampleBuffer();
+ if (buffer == nullptr) {
+ buffer = new (zone) ProcessedSampleBuffer();
+ }
const intptr_t length = capacity();
for (intptr_t i = 0; i < length; i++) {
@@ -1498,12 +1626,6 @@
// An inner sample in a chain of samples.
continue;
}
- // If we're requesting all the native allocation samples, we don't care
- // whether or not we're in the same isolate as the sample.
- if (sample->port() != filter->port()) {
- // Another isolate.
- continue;
- }
if (sample->timestamp() == 0) {
// Empty.
continue;
@@ -1512,17 +1634,25 @@
// No frames.
continue;
}
- if (!filter->TimeFilterSample(sample)) {
- // Did not pass time filter.
- continue;
- }
- if (!filter->TaskFilterSample(sample)) {
- // Did not pass task filter.
- continue;
- }
- if (!filter->FilterSample(sample)) {
- // Did not pass filter.
- continue;
+ if (filter != nullptr) {
+ // If we're requesting all the native allocation samples, we don't care
+ // whether or not we're in the same isolate as the sample.
+ if (sample->port() != filter->port()) {
+ // Another isolate.
+ continue;
+ }
+ if (!filter->TimeFilterSample(sample)) {
+ // Did not pass time filter.
+ continue;
+ }
+ if (!filter->TaskFilterSample(sample)) {
+ // Did not pass task filter.
+ continue;
+ }
+ if (!filter->FilterSample(sample)) {
+ // Did not pass filter.
+ continue;
+ }
}
buffer->Add(BuildProcessedSample(sample, buffer->code_lookup_table()));
}
@@ -1577,7 +1707,7 @@
Sample* SampleBuffer::Next(Sample* sample) {
if (!sample->is_continuation_sample()) return NULL;
- Sample* next_sample = At(sample->continuation_index());
+ Sample* next_sample = sample->continuation_sample();
// Sanity check.
ASSERT(sample != next_sample);
// Detect invalid chaining.
diff --git a/runtime/vm/profiler.h b/runtime/vm/profiler.h
index 2f9ea17..a4adbff4 100644
--- a/runtime/vm/profiler.h
+++ b/runtime/vm/profiler.h
@@ -29,7 +29,7 @@
class Sample;
class AllocationSampleBuffer;
-class SampleBuffer;
+class SampleBlock;
class ProfileTrieNode;
#define PROFILER_COUNTERS(V) \
@@ -46,7 +46,8 @@
V(incomplete_sample_fp_bounds) \
V(incomplete_sample_fp_step) \
V(incomplete_sample_bad_pc) \
- V(failure_native_allocation_sample)
+ V(failure_native_allocation_sample) \
+ V(sample_allocation_failure)
struct ProfilerCounters {
#define DECLARE_PROFILER_COUNTER(name) RelaxedAtomic<int64_t> name;
@@ -69,7 +70,9 @@
// service protocol.
static void UpdateRunningState();
- static SampleBuffer* sample_buffer() { return sample_buffer_; }
+ static SampleBlockBuffer* sample_block_buffer() {
+ return sample_block_buffer_;
+ }
static AllocationSampleBuffer* allocation_sample_buffer() {
return allocation_sample_buffer_;
}
@@ -111,10 +114,12 @@
static intptr_t CalculateSampleBufferCapacity();
// Does not walk the thread's stack.
- static void SampleThreadSingleFrame(Thread* thread, uintptr_t pc);
+ static void SampleThreadSingleFrame(Thread* thread,
+ Sample* sample,
+ uintptr_t pc);
static RelaxedAtomic<bool> initialized_;
- static SampleBuffer* sample_buffer_;
+ static SampleBlockBuffer* sample_block_buffer_;
static AllocationSampleBuffer* allocation_sample_buffer_;
static ProfilerCounters counters_;
@@ -129,6 +134,8 @@
virtual void VisitSample(Sample* sample) = 0;
+ virtual void Reset() { visited_ = 0; }
+
intptr_t visited() const { return visited_; }
void IncrementVisited() { visited_++; }
@@ -189,11 +196,14 @@
// Each Sample holds a stack trace from an isolate.
class Sample {
public:
+ Sample() = default;
+
void Init(Dart_Port port, int64_t timestamp, ThreadId tid) {
Clear();
timestamp_ = timestamp;
tid_ = tid;
port_ = port;
+ next_ = nullptr;
}
Dart_Port port() const { return port_; }
@@ -214,7 +224,7 @@
vm_tag_ = VMTag::kInvalidTagId;
user_tag_ = UserTags::kDefaultUserTag;
state_ = 0;
- continuation_index_ = -1;
+ next_ = nullptr;
allocation_identity_hash_ = 0;
#if defined(DART_USE_TCMALLOC) && defined(DEBUG)
native_allocation_address_ = 0;
@@ -352,18 +362,14 @@
return ContinuationSampleBit::decode(state_);
}
- void SetContinuationIndex(intptr_t index) {
+ void SetContinuation(Sample* next) {
ASSERT(!is_continuation_sample());
- ASSERT(continuation_index_ == -1);
+ ASSERT(next_ == nullptr);
state_ = ContinuationSampleBit::update(true, state_);
- continuation_index_ = index;
- ASSERT(is_continuation_sample());
+ next_ = next;
}
- intptr_t continuation_index() const {
- ASSERT(is_continuation_sample());
- return continuation_index_;
- }
+ Sample* continuation_sample() const { return next_; }
intptr_t allocation_cid() const {
ASSERT(is_allocation_sample());
@@ -431,7 +437,7 @@
uword vm_tag_;
uword user_tag_;
uint32_t state_;
- int32_t continuation_index_;
+ Sample* next_;
uint32_t allocation_identity_hash_;
#if defined(DART_USE_TCMALLOC) && defined(DEBUG)
@@ -623,21 +629,26 @@
DISALLOW_COPY_AND_ASSIGN(CodeLookupTable);
};
-// Ring buffer of Samples that is (usually) shared by many isolates.
-class SampleBuffer {
+// Interface for a class that can create a ProcessedSampleBuffer.
+class ProcessedSampleBufferBuilder {
public:
- // Up to 1 minute @ 1000Hz, less if samples are deep.
- static const intptr_t kDefaultBufferCapacity = 60000;
+ virtual ~ProcessedSampleBufferBuilder() = default;
+ virtual ProcessedSampleBuffer* BuildProcessedSampleBuffer(
+ SampleFilter* filter,
+ ProcessedSampleBuffer* buffer = nullptr) = 0;
+};
- explicit SampleBuffer(intptr_t capacity = kDefaultBufferCapacity);
- virtual ~SampleBuffer();
+class SampleBuffer : public ProcessedSampleBufferBuilder {
+ public:
+ SampleBuffer() = default;
+ virtual ~SampleBuffer() = default;
- intptr_t capacity() const { return capacity_; }
-
- Sample* At(intptr_t idx) const;
- intptr_t ReserveSampleSlot();
- virtual Sample* ReserveSample();
- virtual Sample* ReserveSampleAndLink(Sample* previous);
+ virtual void Init(Sample* samples, intptr_t capacity) {
+ ASSERT(samples != nullptr);
+ ASSERT(capacity > 0);
+ samples_ = samples;
+ capacity_ = capacity;
+ }
void VisitSamples(SampleVisitor* visitor) {
ASSERT(visitor != NULL);
@@ -669,45 +680,218 @@
}
}
- ProcessedSampleBuffer* BuildProcessedSampleBuffer(SampleFilter* filter);
+ virtual Sample* ReserveSample() = 0;
+ virtual Sample* ReserveSampleAndLink(Sample* previous) = 0;
- intptr_t Size() { return memory_->size(); }
+ Sample* At(intptr_t idx) const {
+ ASSERT(idx >= 0);
+ ASSERT(idx < capacity_);
+ return &samples_[idx];
+ }
+
+ intptr_t capacity() const { return capacity_; }
+
+ virtual ProcessedSampleBuffer* BuildProcessedSampleBuffer(
+ SampleFilter* filter,
+ ProcessedSampleBuffer* buffer = nullptr);
protected:
- ProcessedSample* BuildProcessedSample(Sample* sample,
- const CodeLookupTable& clt);
Sample* Next(Sample* sample);
- VirtualMemory* memory_;
+ ProcessedSample* BuildProcessedSample(Sample* sample,
+ const CodeLookupTable& clt);
+
Sample* samples_;
intptr_t capacity_;
- RelaxedAtomic<uintptr_t> cursor_;
+
+ DISALLOW_COPY_AND_ASSIGN(SampleBuffer);
+};
+
+class SampleBlock : public SampleBuffer {
+ public:
+ // The default number of samples per block. Overridden by some tests.
+ static const intptr_t kSamplesPerBlock = 1000;
+
+ SampleBlock() = default;
+ virtual ~SampleBlock() = default;
+
+ void Clear() {
+ allocation_block_ = false;
+ cursor_ = 0;
+ full_ = false;
+ evictable_ = false;
+ next_free_ = nullptr;
+ }
+
+ // Returns the number of samples contained within this block.
+ intptr_t capacity() const { return capacity_; }
+
+ // Specify whether or not this block is used for assigning allocation
+ // samples.
+ void set_is_allocation_block(bool is_allocation_block) {
+ allocation_block_ = is_allocation_block;
+ }
+
+ Isolate* owner() const { return owner_; }
+ void set_owner(Isolate* isolate) { owner_ = isolate; }
+
+ // Manually marks the block as full so it can be processed and added back to
+ // the pool of available blocks.
+ void release_block() { full_.store(true); }
+
+ // When true, this sample block is considered complete and will no longer be
+ // used to assign new Samples. This block is **not** available for
+ // re-allocation simply because it's full. It must be processed by
+ // SampleBlockBuffer::ProcessCompletedBlocks before it can be considered
+ // evictable and available for re-allocation.
+ bool is_full() const { return full_.load(); }
+
+ // When true, this sample block is available for re-allocation.
+ bool evictable() const { return evictable_.load(); }
+
+ virtual Sample* ReserveSample();
+ virtual Sample* ReserveSampleAndLink(Sample* previous);
+
+ protected:
+ Isolate* owner_ = nullptr;
+ bool allocation_block_ = false;
+
+ intptr_t index_;
+ RelaxedAtomic<int> cursor_ = 0;
+ RelaxedAtomic<bool> full_ = false;
+ RelaxedAtomic<bool> evictable_ = false;
+
+ SampleBlock* next_free_ = nullptr;
private:
- DISALLOW_COPY_AND_ASSIGN(SampleBuffer);
+ friend class SampleBlockBuffer;
+
+ DISALLOW_COPY_AND_ASSIGN(SampleBlock);
+};
+
+class SampleBlockBuffer : public ProcessedSampleBufferBuilder {
+ public:
+ static const intptr_t kDefaultBlockCount = 60;
+
+ // Creates a SampleBlockBuffer with a predetermined number of blocks.
+ //
+ // Defaults to kDefaultBlockCount blocks. Block size is fixed to
+ // SampleBlock::kSamplesPerBlock samples per block, except for in tests.
+ explicit SampleBlockBuffer(
+ intptr_t blocks = kDefaultBlockCount,
+ intptr_t samples_per_block = SampleBlock::kSamplesPerBlock);
+
+ virtual ~SampleBlockBuffer();
+
+ void VisitSamples(SampleVisitor* visitor) {
+ ASSERT(visitor != NULL);
+ for (intptr_t i = 0; i < cursor_.load(); ++i) {
+ (&blocks_[i])->VisitSamples(visitor);
+ }
+ }
+
+ // Returns true when there is at least a single block that needs to be
+ // processed.
+ //
+ // NOTE: this should only be called from the interrupt handler as
+ // invocation will have the side effect of clearing the underlying flag.
+ bool process_blocks() { return can_process_block_.exchange(false); }
+
+ // Iterates over the blocks in the buffer and processes blocks marked as
+ // full. Processing consists of sending a service event with the samples from
+ // completed, unprocessed blocks and marking these blocks are evictable
+ // (i.e., safe to be re-allocated and re-used).
+ void ProcessCompletedBlocks();
+
+ // Reserves a sample for a CPU profile.
+ //
+ // Returns nullptr when a sample can't be reserved.
+ Sample* ReserveCPUSample(Isolate* isolate);
+
+ // Reserves a sample for a Dart object allocation profile.
+ //
+ // Returns nullptr when a sample can't be reserved.
+ Sample* ReserveAllocationSample(Isolate* isolate);
+
+ intptr_t Size() const { return memory_->size(); }
+
+ virtual ProcessedSampleBuffer* BuildProcessedSampleBuffer(
+ SampleFilter* filter,
+ ProcessedSampleBuffer* buffer = nullptr);
+
+ private:
+ Sample* ReserveSampleImpl(Isolate* isolate, bool allocation_sample);
+
+ // Returns nullptr if there are no available blocks.
+ SampleBlock* ReserveSampleBlock();
+
+ void FreeBlock(SampleBlock* block) {
+ ASSERT(block->next_free_ == nullptr);
+ MutexLocker ml(&free_block_lock_);
+ if (free_list_head_ == nullptr) {
+ free_list_head_ = block;
+ free_list_tail_ = block;
+ return;
+ }
+ free_list_tail_->next_free_ = block;
+ free_list_tail_ = block;
+ }
+
+ SampleBlock* GetFreeBlock() {
+ MutexLocker ml(&free_block_lock_);
+ if (free_list_head_ == nullptr) {
+ return nullptr;
+ }
+ SampleBlock* block = free_list_head_;
+ free_list_head_ = block->next_free_;
+ if (free_list_head_ == nullptr) {
+ free_list_tail_ = nullptr;
+ }
+ block->next_free_ = nullptr;
+ return block;
+ }
+
+ Mutex free_block_lock_;
+ RelaxedAtomic<bool> can_process_block_ = false;
+
+ // Sample block management.
+ RelaxedAtomic<int> cursor_;
+ SampleBlock* blocks_;
+ intptr_t capacity_;
+ SampleBlock* free_list_head_;
+ SampleBlock* free_list_tail_;
+
+ // Sample buffer management.
+ VirtualMemory* memory_;
+ Sample* sample_buffer_;
+ DISALLOW_COPY_AND_ASSIGN(SampleBlockBuffer);
};
class AllocationSampleBuffer : public SampleBuffer {
public:
- explicit AllocationSampleBuffer(intptr_t capacity = kDefaultBufferCapacity);
- virtual ~AllocationSampleBuffer();
+ explicit AllocationSampleBuffer(intptr_t capacity = 60000);
+ virtual ~AllocationSampleBuffer() = default;
- intptr_t ReserveSampleSlotLocked();
virtual Sample* ReserveSample();
virtual Sample* ReserveSampleAndLink(Sample* previous);
void FreeAllocationSample(Sample* sample);
+ intptr_t Size() { return memory_->size(); }
+
private:
+ intptr_t ReserveSampleSlotLocked();
+
Mutex mutex_;
Sample* free_sample_list_;
-
+ VirtualMemory* memory_;
+ RelaxedAtomic<int> cursor_ = 0;
DISALLOW_COPY_AND_ASSIGN(AllocationSampleBuffer);
};
intptr_t Profiler::Size() {
intptr_t size = 0;
- if (sample_buffer_ != nullptr) {
- size += sample_buffer_->Size();
+ if (sample_block_buffer_ != nullptr) {
+ size += sample_block_buffer_->Size();
}
if (allocation_sample_buffer_ != nullptr) {
size += allocation_sample_buffer_->Size();
diff --git a/runtime/vm/profiler_service.cc b/runtime/vm/profiler_service.cc
index a61fb94..e9f333e 100644
--- a/runtime/vm/profiler_service.cc
+++ b/runtime/vm/profiler_service.cc
@@ -7,6 +7,7 @@
#include "platform/text_buffer.h"
#include "vm/growable_array.h"
#include "vm/hash_map.h"
+#include "vm/heap/safepoint.h"
#include "vm/log.h"
#include "vm/malloc_hooks.h"
#include "vm/native_symbol.h"
@@ -15,6 +16,8 @@
#include "vm/profiler.h"
#include "vm/reusable_handles.h"
#include "vm/scope_timer.h"
+#include "vm/service.h"
+#include "vm/service_event.h"
#include "vm/timeline.h"
namespace dart {
@@ -919,7 +922,7 @@
ProfileBuilder(Thread* thread,
SampleFilter* filter,
- SampleBuffer* sample_buffer,
+ ProcessedSampleBufferBuilder* sample_buffer,
Profile* profile)
: thread_(thread),
vm_isolate_(Dart::vm_isolate()),
@@ -932,8 +935,6 @@
inlined_functions_cache_(new ProfileCodeInlinedFunctionsCache()),
samples_(NULL),
info_kind_(kNone) {
- ASSERT((sample_buffer_ == Profiler::sample_buffer()) ||
- (sample_buffer_ == Profiler::allocation_sample_buffer()));
ASSERT(profile_ != NULL);
}
@@ -975,7 +976,7 @@
bool FilterSamples() {
ScopeTimer sw("ProfileBuilder::FilterSamples", FLAG_trace_profiler);
- ASSERT(sample_buffer_ != NULL);
+ ASSERT(sample_buffer_ != nullptr);
samples_ = sample_buffer_->BuildProcessedSampleBuffer(filter_);
profile_->samples_ = samples_;
profile_->sample_count_ = samples_->length();
@@ -1439,7 +1440,7 @@
Thread* thread_;
Isolate* vm_isolate_;
SampleFilter* filter_;
- SampleBuffer* sample_buffer_;
+ ProcessedSampleBufferBuilder* sample_buffer_;
Profile* profile_;
const AbstractCode null_code_;
const Function& null_function_;
@@ -1466,11 +1467,10 @@
void Profile::Build(Thread* thread,
SampleFilter* filter,
- SampleBuffer* sample_buffer) {
+ ProcessedSampleBufferBuilder* sample_buffer) {
// Disable thread interrupts while processing the buffer.
DisableThreadInterruptsScope dtis(thread);
ThreadInterrupter::SampleBufferReaderScope scope;
-
ProfileBuilder builder(thread, filter, sample_buffer, this);
builder.Build();
}
@@ -1722,12 +1722,16 @@
}
void Profile::PrintProfileJSON(JSONStream* stream, bool include_code_samples) {
- ScopeTimer sw("Profile::PrintProfileJSON", FLAG_trace_profiler);
JSONObject obj(stream);
- obj.AddProperty("type", "CpuSamples");
- PrintHeaderJSON(&obj);
+ PrintProfileJSON(&obj, include_code_samples);
+}
+
+void Profile::PrintProfileJSON(JSONObject* obj, bool include_code_samples) {
+ ScopeTimer sw("Profile::PrintProfileJSON", FLAG_trace_profiler);
+ obj->AddProperty("type", "CpuSamples");
+ PrintHeaderJSON(obj);
if (include_code_samples) {
- JSONArray codes(&obj, "_codes");
+ JSONArray codes(obj, "_codes");
for (intptr_t i = 0; i < live_code_->length(); i++) {
ProfileCode* code = live_code_->At(i);
ASSERT(code != NULL);
@@ -1746,30 +1750,30 @@
}
{
- JSONArray functions(&obj, "functions");
+ JSONArray functions(obj, "functions");
for (intptr_t i = 0; i < functions_->length(); i++) {
ProfileFunction* function = functions_->At(i);
ASSERT(function != NULL);
function->PrintToJSONArray(&functions);
}
}
- PrintSamplesJSON(&obj, include_code_samples);
+ PrintSamplesJSON(obj, include_code_samples);
}
void ProfilerService::PrintJSONImpl(Thread* thread,
JSONStream* stream,
SampleFilter* filter,
- SampleBuffer* sample_buffer,
+ ProcessedSampleBufferBuilder* buffer,
bool include_code_samples) {
Isolate* isolate = thread->isolate();
// We should bail out in service.cc if the profiler is disabled.
- ASSERT(sample_buffer != NULL);
+ ASSERT(buffer != nullptr);
StackZone zone(thread);
HANDLESCOPE(thread);
Profile profile(isolate);
- profile.Build(thread, filter, sample_buffer);
+ profile.Build(thread, filter, buffer);
profile.PrintProfileJSON(stream, include_code_samples);
}
@@ -1795,7 +1799,7 @@
Isolate* isolate = thread->isolate();
NoAllocationSampleFilter filter(isolate->main_port(), Thread::kMutatorTask,
time_origin_micros, time_extent_micros);
- PrintJSONImpl(thread, stream, &filter, Profiler::sample_buffer(),
+ PrintJSONImpl(thread, stream, &filter, Profiler::sample_block_buffer(),
include_code_samples);
}
@@ -1820,7 +1824,7 @@
Isolate* isolate = thread->isolate();
AllocationSampleFilter filter(isolate->main_port(), Thread::kMutatorTask,
time_origin_micros, time_extent_micros);
- PrintJSONImpl(thread, stream, &filter, Profiler::sample_buffer(), true);
+ PrintJSONImpl(thread, stream, &filter, Profiler::sample_block_buffer(), true);
}
class ClassAllocationSampleFilter : public SampleFilter {
@@ -1856,7 +1860,7 @@
ClassAllocationSampleFilter filter(isolate->main_port(), cls,
Thread::kMutatorTask, time_origin_micros,
time_extent_micros);
- PrintJSONImpl(thread, stream, &filter, Profiler::sample_buffer(), true);
+ PrintJSONImpl(thread, stream, &filter, Profiler::sample_block_buffer(), true);
}
void ProfilerService::PrintNativeAllocationJSON(JSONStream* stream,
@@ -1870,8 +1874,8 @@
}
void ProfilerService::ClearSamples() {
- SampleBuffer* sample_buffer = Profiler::sample_buffer();
- if (sample_buffer == NULL) {
+ SampleBlockBuffer* sample_block_buffer = Profiler::sample_block_buffer();
+ if (sample_block_buffer == nullptr) {
return;
}
@@ -1883,7 +1887,7 @@
ThreadInterrupter::SampleBufferReaderScope scope;
ClearProfileVisitor clear_profile(isolate);
- sample_buffer->VisitSamples(&clear_profile);
+ sample_block_buffer->VisitSamples(&clear_profile);
}
#endif // !PRODUCT
diff --git a/runtime/vm/profiler_service.h b/runtime/vm/profiler_service.h
index 8009207..1c02db3 100644
--- a/runtime/vm/profiler_service.h
+++ b/runtime/vm/profiler_service.h
@@ -367,7 +367,9 @@
explicit Profile(Isolate* isolate);
// Build a filtered model using |filter|.
- void Build(Thread* thread, SampleFilter* filter, SampleBuffer* sample_buffer);
+ void Build(Thread* thread,
+ SampleFilter* filter,
+ ProcessedSampleBufferBuilder* sample_block_buffer);
// After building:
int64_t min_time() const { return min_time_; }
@@ -383,6 +385,7 @@
ProfileCode* GetCodeFromPC(uword pc, int64_t timestamp);
void PrintProfileJSON(JSONStream* stream, bool include_code_samples);
+ void PrintProfileJSON(JSONObject* obj, bool include_code_samples);
ProfileFunction* FindFunction(const Function& function);
@@ -445,7 +448,7 @@
static void PrintJSONImpl(Thread* thread,
JSONStream* stream,
SampleFilter* filter,
- SampleBuffer* sample_buffer,
+ ProcessedSampleBufferBuilder* buffer,
bool include_code_samples);
};
diff --git a/runtime/vm/profiler_test.cc b/runtime/vm/profiler_test.cc
index 1829169..1171458 100644
--- a/runtime/vm/profiler_test.cc
+++ b/runtime/vm/profiler_test.cc
@@ -22,6 +22,12 @@
DECLARE_FLAG(int, max_profile_depth);
DECLARE_FLAG(int, optimization_counter_threshold);
+// SampleVisitor ignores samples with timestamp == 0.
+const int64_t kValidTimeStamp = 1;
+
+// SampleVisitor ignores samples with pc == 0.
+const uword kValidPc = 0xFF;
+
// Some tests are written assuming native stack trace profiling is disabled.
class DisableNativeProfileScope : public ValueObject {
public:
@@ -71,87 +77,123 @@
const intptr_t FLAG_max_profile_depth_;
};
-class ProfileSampleBufferTestHelper {
+class ProfileSampleBufferTestHelper : public SampleVisitor {
public:
- static intptr_t IterateCount(const Dart_Port port,
- const SampleBuffer& sample_buffer) {
- intptr_t c = 0;
- for (intptr_t i = 0; i < sample_buffer.capacity(); i++) {
- Sample* sample = sample_buffer.At(i);
- if (sample->port() != port) {
- continue;
- }
- c++;
- }
- return c;
+ explicit ProfileSampleBufferTestHelper(Dart_Port port)
+ : SampleVisitor(port) {}
+
+ void VisitSample(Sample* sample) { sum_ += sample->At(0); }
+
+ void Reset() {
+ sum_ = 0;
+ SampleVisitor::Reset();
}
- static intptr_t IterateSumPC(const Dart_Port port,
- const SampleBuffer& sample_buffer) {
- intptr_t c = 0;
- for (intptr_t i = 0; i < sample_buffer.capacity(); i++) {
- Sample* sample = sample_buffer.At(i);
- if (sample->port() != port) {
- continue;
- }
- c += sample->At(0);
- }
- return c;
- }
+ intptr_t sum() const { return sum_; }
+
+ private:
+ intptr_t sum_ = 0;
};
+void VisitSamples(SampleBlockBuffer* buffer, SampleVisitor* visitor) {
+ // Mark the completed blocks as free so they can be re-used.
+ buffer->ProcessCompletedBlocks();
+ visitor->Reset();
+ buffer->VisitSamples(visitor);
+}
+
TEST_CASE(Profiler_SampleBufferWrapTest) {
- SampleBuffer* sample_buffer = new SampleBuffer(3);
+ Isolate* isolate = Isolate::Current();
+ SampleBlockBuffer* sample_buffer = new SampleBlockBuffer(3, 1);
Dart_Port i = 123;
- EXPECT_EQ(0, ProfileSampleBufferTestHelper::IterateSumPC(i, *sample_buffer));
+ ProfileSampleBufferTestHelper visitor(i);
+
+ VisitSamples(sample_buffer, &visitor);
+ EXPECT_EQ(0, visitor.sum());
Sample* s;
- s = sample_buffer->ReserveSample();
- s->Init(i, 0, 0);
+
+ s = sample_buffer->ReserveCPUSample(isolate);
+ s->Init(i, kValidTimeStamp, 0);
s->SetAt(0, 2);
- EXPECT_EQ(2, ProfileSampleBufferTestHelper::IterateSumPC(i, *sample_buffer));
- s = sample_buffer->ReserveSample();
- s->Init(i, 0, 0);
+ VisitSamples(sample_buffer, &visitor);
+ EXPECT_EQ(2, visitor.sum());
+
+ s = sample_buffer->ReserveCPUSample(isolate);
+ s->Init(i, kValidTimeStamp, 0);
s->SetAt(0, 4);
- EXPECT_EQ(6, ProfileSampleBufferTestHelper::IterateSumPC(i, *sample_buffer));
- s = sample_buffer->ReserveSample();
- s->Init(i, 0, 0);
+ VisitSamples(sample_buffer, &visitor);
+ EXPECT_EQ(6, visitor.sum());
+
+ s = sample_buffer->ReserveCPUSample(isolate);
+ s->Init(i, kValidTimeStamp, 0);
s->SetAt(0, 6);
- EXPECT_EQ(12, ProfileSampleBufferTestHelper::IterateSumPC(i, *sample_buffer));
- s = sample_buffer->ReserveSample();
- s->Init(i, 0, 0);
+ VisitSamples(sample_buffer, &visitor);
+ EXPECT_EQ(12, visitor.sum());
+
+ // Mark the completed blocks as free so they can be re-used.
+ sample_buffer->ProcessCompletedBlocks();
+
+ s = sample_buffer->ReserveCPUSample(isolate);
+ s->Init(i, kValidTimeStamp, 0);
s->SetAt(0, 8);
- EXPECT_EQ(18, ProfileSampleBufferTestHelper::IterateSumPC(i, *sample_buffer));
+ VisitSamples(sample_buffer, &visitor);
+ EXPECT_EQ(18, visitor.sum());
+ {
+ MutexLocker ml(isolate->current_sample_block_lock());
+ isolate->set_current_sample_block(nullptr);
+ }
delete sample_buffer;
}
TEST_CASE(Profiler_SampleBufferIterateTest) {
- SampleBuffer* sample_buffer = new SampleBuffer(3);
+ Isolate* isolate = Isolate::Current();
+ SampleBlockBuffer* sample_buffer = new SampleBlockBuffer(3, 1);
Dart_Port i = 123;
- EXPECT_EQ(0, ProfileSampleBufferTestHelper::IterateCount(i, *sample_buffer));
+ ProfileSampleBufferTestHelper visitor(i);
+
+ sample_buffer->VisitSamples(&visitor);
+ EXPECT_EQ(0, visitor.visited());
Sample* s;
- s = sample_buffer->ReserveSample();
- s->Init(i, 0, 0);
- EXPECT_EQ(1, ProfileSampleBufferTestHelper::IterateCount(i, *sample_buffer));
- s = sample_buffer->ReserveSample();
- s->Init(i, 0, 0);
- EXPECT_EQ(2, ProfileSampleBufferTestHelper::IterateCount(i, *sample_buffer));
- s = sample_buffer->ReserveSample();
- s->Init(i, 0, 0);
- EXPECT_EQ(3, ProfileSampleBufferTestHelper::IterateCount(i, *sample_buffer));
- s = sample_buffer->ReserveSample();
- s->Init(i, 0, 0);
- EXPECT_EQ(3, ProfileSampleBufferTestHelper::IterateCount(i, *sample_buffer));
+ s = sample_buffer->ReserveCPUSample(isolate);
+ s->Init(i, kValidTimeStamp, 0);
+ s->SetAt(0, kValidPc);
+ VisitSamples(sample_buffer, &visitor);
+ EXPECT_EQ(1, visitor.visited());
+
+ s = sample_buffer->ReserveCPUSample(isolate);
+ s->Init(i, kValidTimeStamp, 0);
+ s->SetAt(0, kValidPc);
+ VisitSamples(sample_buffer, &visitor);
+ EXPECT_EQ(2, visitor.visited());
+
+ s = sample_buffer->ReserveCPUSample(isolate);
+ s->Init(i, kValidTimeStamp, 0);
+ s->SetAt(0, kValidPc);
+ VisitSamples(sample_buffer, &visitor);
+ EXPECT_EQ(3, visitor.visited());
+
+ s = sample_buffer->ReserveCPUSample(isolate);
+ s->Init(i, kValidTimeStamp, 0);
+ s->SetAt(0, kValidPc);
+ VisitSamples(sample_buffer, &visitor);
+ EXPECT_EQ(3, visitor.visited());
+
+ {
+ MutexLocker ml(isolate->current_sample_block_lock());
+ isolate->set_current_sample_block(nullptr);
+ }
delete sample_buffer;
}
TEST_CASE(Profiler_AllocationSampleTest) {
Isolate* isolate = Isolate::Current();
- SampleBuffer* sample_buffer = new SampleBuffer(3);
- Sample* sample = sample_buffer->ReserveSample();
+ SampleBlockBuffer* sample_buffer = new SampleBlockBuffer(1, 1);
+ Sample* sample = sample_buffer->ReserveAllocationSample(isolate);
sample->Init(isolate->main_port(), 0, 0);
sample->set_metadata(99);
sample->set_is_allocation_sample(true);
EXPECT_EQ(99, sample->allocation_cid());
+ isolate->set_current_allocation_sample_block(nullptr);
delete sample_buffer;
}
@@ -443,7 +485,7 @@
AllocationFilter filter(isolate->main_port(), class_a.id(),
before_allocations_micros,
allocation_extent_micros);
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have 1 allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile);
@@ -471,7 +513,7 @@
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id(),
Dart_TimelineGetMicros(), 16000);
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples because none occured within
// the specified time range.
EXPECT_EQ(0, profile.sample_count());
@@ -561,7 +603,7 @@
// Filter for the class in the time range.
NativeAllocationSampleFilter filter(before_allocations_micros,
allocation_extent_micros);
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have 0 allocation samples since we freed the memory.
EXPECT_EQ(0, profile.sample_count());
}
@@ -574,7 +616,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
NativeAllocationSampleFilter filter(Dart_TimelineGetMicros(), 16000);
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples because none occured within
// the specified time range.
EXPECT_EQ(0, profile.sample_count());
@@ -620,7 +662,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
EXPECT_EQ(0, profile.sample_count());
}
@@ -637,7 +679,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile);
@@ -667,7 +709,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
}
@@ -705,7 +747,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
EXPECT_EQ(0, profile.sample_count());
}
@@ -725,7 +767,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have three allocation samples.
EXPECT_EQ(3, profile.sample_count());
ProfileStackWalker walker(&profile);
@@ -780,7 +822,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
EXPECT_EQ(0, profile.sample_count());
}
@@ -800,7 +842,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have three allocation samples.
EXPECT_EQ(3, profile.sample_count());
ProfileStackWalker walker(&profile, true);
@@ -850,7 +892,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), double_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
EXPECT_EQ(0, profile.sample_count());
}
@@ -863,7 +905,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), double_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile);
@@ -885,7 +927,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), double_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
}
@@ -912,7 +954,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), array_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
EXPECT_EQ(0, profile.sample_count());
}
@@ -925,7 +967,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), array_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile);
@@ -947,7 +989,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), array_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
}
@@ -969,7 +1011,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), array_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples, since empty
// growable lists use a shared backing.
EXPECT_EQ(0, profile.sample_count());
@@ -999,7 +1041,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), context_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
EXPECT_EQ(0, profile.sample_count());
}
@@ -1012,7 +1054,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), context_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile);
@@ -1032,7 +1074,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), context_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
}
@@ -1073,7 +1115,7 @@
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), closure_class.id());
filter.set_enable_vm_ticks(true);
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile);
@@ -1097,7 +1139,7 @@
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), closure_class.id());
filter.set_enable_vm_ticks(true);
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
}
@@ -1127,7 +1169,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), float32_list_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
EXPECT_EQ(0, profile.sample_count());
}
@@ -1140,7 +1182,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), float32_list_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile);
@@ -1162,7 +1204,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), float32_list_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
}
@@ -1175,7 +1217,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), float32_list_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should now have two allocation samples.
EXPECT_EQ(2, profile.sample_count());
}
@@ -1207,7 +1249,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
EXPECT_EQ(0, profile.sample_count());
}
@@ -1220,7 +1262,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile);
@@ -1240,7 +1282,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
}
@@ -1253,7 +1295,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should now have two allocation samples.
EXPECT_EQ(2, profile.sample_count());
}
@@ -1285,7 +1327,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
EXPECT_EQ(0, profile.sample_count());
}
@@ -1298,7 +1340,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile);
@@ -1324,7 +1366,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should still only have one allocation sample.
EXPECT_EQ(1, profile.sample_count());
}
@@ -1337,7 +1379,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), one_byte_string_class.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should now have two allocation samples.
EXPECT_EQ(2, profile.sample_count());
}
@@ -1393,7 +1435,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
EXPECT_EQ(0, profile.sample_count());
}
@@ -1411,7 +1453,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have 50,000 allocation samples.
EXPECT_EQ(50000, profile.sample_count());
{
@@ -1540,7 +1582,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have no allocation samples.
EXPECT_EQ(0, profile.sample_count());
}
@@ -1557,7 +1599,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile, true);
@@ -1633,7 +1675,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have 1 allocation sample.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile);
@@ -1728,7 +1770,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation samples.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile, true);
@@ -1810,7 +1852,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation samples.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile, true);
@@ -1888,7 +1930,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation samples.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile, true);
@@ -1998,7 +2040,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation samples.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile, true);
@@ -2093,7 +2135,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation samples.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile, true);
@@ -2211,7 +2253,7 @@
HANDLESCOPE(thread);
Profile profile(isolate);
AllocationFilter filter(isolate->main_port(), class_a.id());
- profile.Build(thread, &filter, Profiler::sample_buffer());
+ profile.Build(thread, &filter, Profiler::sample_block_buffer());
// We should have one allocation samples.
EXPECT_EQ(1, profile.sample_count());
ProfileStackWalker walker(&profile, true);
@@ -2256,10 +2298,10 @@
}
}
-static void InsertFakeSample(SampleBuffer* sample_buffer, uword* pc_offsets) {
- ASSERT(sample_buffer != NULL);
+static void InsertFakeSample(uword* pc_offsets) {
Isolate* isolate = Isolate::Current();
- Sample* sample = sample_buffer->ReserveSample();
+ ASSERT(Profiler::sample_block_buffer() != nullptr);
+ Sample* sample = Profiler::sample_block_buffer()->ReserveCPUSample(isolate);
ASSERT(sample != NULL);
sample->Init(isolate->main_port(), OS::GetCurrentMonotonicMicros(),
OSThread::Current()->trace_id());
@@ -2319,8 +2361,8 @@
DisableBackgroundCompilationScope dbcs;
- SampleBuffer* sample_buffer = Profiler::sample_buffer();
- EXPECT(sample_buffer != NULL);
+ SampleBlockBuffer* sample_block_buffer = Profiler::sample_block_buffer();
+ ASSERT(sample_block_buffer != nullptr);
const Library& root_library = Library::Handle(LoadTestScript(kScript));
@@ -2330,7 +2372,7 @@
{
// Clear the profile for this isolate.
ClearProfileVisitor cpv(Isolate::Current());
- sample_buffer->VisitSamples(&cpv);
+ sample_block_buffer->VisitSamples(&cpv);
}
// Query the code object for main and determine the PC at some token
@@ -2390,9 +2432,9 @@
callPositionPc, // main.
0};
- InsertFakeSample(sample_buffer, &sample1[0]);
- InsertFakeSample(sample_buffer, &sample2[0]);
- InsertFakeSample(sample_buffer, &sample3[0]);
+ InsertFakeSample(&sample1[0]);
+ InsertFakeSample(&sample2[0]);
+ InsertFakeSample(&sample3[0]);
// Generate source report for main.
JSONStream js;
diff --git a/runtime/vm/service.h b/runtime/vm/service.h
index e178c6a..7f5a223 100644
--- a/runtime/vm/service.h
+++ b/runtime/vm/service.h
@@ -15,7 +15,7 @@
namespace dart {
#define SERVICE_PROTOCOL_MAJOR_VERSION 3
-#define SERVICE_PROTOCOL_MINOR_VERSION 48
+#define SERVICE_PROTOCOL_MINOR_VERSION 49
class Array;
class EmbedderServiceHandler;
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index 708f581..fe8233a 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -1,8 +1,8 @@
-# Dart VM Service Protocol 3.48
+# Dart VM Service Protocol 3.49
> Please post feedback to the [observatory-discuss group][discuss-list]
-This document describes of _version 3.48_ of the Dart VM Service Protocol. This
+This document describes of _version 3.49_ of the Dart VM Service Protocol. This
protocol is used to communicate with a running Dart Virtual Machine.
To use the Service Protocol, start the VM with the *--observe* flag.
@@ -1477,7 +1477,7 @@
VM | VMUpdate, VMFlagUpdate
Isolate | IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, IsolateReload, ServiceExtensionAdded
Debug | PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted, PauseException, PausePostRequest, Resume, BreakpointAdded, BreakpointResolved, BreakpointRemoved, BreakpointUpdated, Inspect, None
-Profiler | UserTagChanged
+Profiler | CpuSamples, UserTagChanged
GC | GC
Extension | Extension
Timeline | TimelineEvents, TimelineStreamsSubscriptionUpdate
@@ -2161,6 +2161,9 @@
// The previous UserTag label.
string previousTag [optional];
+
+ // A CPU profile containing recent samples.
+ CpuSamples cpuSamples [optional];
}
```
@@ -2274,6 +2277,9 @@
// Notification that the UserTag for an isolate has been changed.
UserTagChanged,
+
+ // A block of recently collected CPU samples.
+ CpuSamples,
}
```
@@ -4069,5 +4075,6 @@
3.46 | Moved `sourceLocation` property into reference types for `Class`, `Field`, and `Function`.
3.47 | Added `shows` and `hides` properties to `LibraryDependency`.
3.48 | Added `Profiler` stream, `UserTagChanged` event kind, and `updatedTag` and `previousTag` properties to `Event`.
+3.49 | Added `CpuSamples` event kind, and `cpuSamples` property to `Event`.
[discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss
diff --git a/runtime/vm/service_event.cc b/runtime/vm/service_event.cc
index f8ab174..85740cf 100644
--- a/runtime/vm/service_event.cc
+++ b/runtime/vm/service_event.cc
@@ -64,7 +64,8 @@
event_kind == ServiceEvent::kNone ||
// VM service can print Observatory information to Stdout or Stderr
// which are embedder streams.
- event_kind == ServiceEvent::kEmbedder)));
+ event_kind == ServiceEvent::kEmbedder ||
+ event_kind == ServiceEvent::kCpuSamples)));
if ((event_kind == ServiceEvent::kPauseStart) ||
(event_kind == ServiceEvent::kPauseExit)) {
@@ -141,6 +142,8 @@
return "TimelineStreamSubscriptionsUpdate";
case kUserTagChanged:
return "UserTagChanged";
+ case kCpuSamples:
+ return "CpuSamples";
default:
UNREACHABLE();
return "Unknown";
@@ -193,6 +196,7 @@
case kEmbedder:
return nullptr;
+ case kCpuSamples:
case kUserTagChanged:
return &Service::profiler_stream;
@@ -300,6 +304,11 @@
js->AppendSerializedObject("extensionData",
extension_event_.event_data->ToCString());
}
+
+ if (kind() == kCpuSamples) {
+ JSONObject cpu_profile(&jsobj, "cpuSamples");
+ cpu_profile_->PrintProfileJSON(&cpu_profile, false);
+ }
}
void ServiceEvent::PrintJSONHeader(JSONObject* jsobj) const {
diff --git a/runtime/vm/service_event.h b/runtime/vm/service_event.h
index 3b8de0e..4c0efdd 100644
--- a/runtime/vm/service_event.h
+++ b/runtime/vm/service_event.h
@@ -7,6 +7,7 @@
#include "vm/globals.h"
#include "vm/heap/heap.h"
+#include "vm/profiler_service.h"
namespace dart {
@@ -62,6 +63,8 @@
kUserTagChanged,
+ kCpuSamples,
+
kIllegal,
};
@@ -213,6 +216,9 @@
timeline_event_block_ = block;
}
+ Profile* cpu_profile() const { return cpu_profile_; }
+ void set_cpu_profile(Profile* profile) { cpu_profile_ = profile; }
+
void PrintJSON(JSONStream* js) const;
void PrintJSONHeader(JSONObject* jsobj) const;
@@ -246,6 +252,7 @@
intptr_t bytes_length_;
LogRecord log_record_;
ExtensionEvent extension_event_;
+ Profile* cpu_profile_;
int64_t timestamp_;
};
diff --git a/runtime/vm/source_report.cc b/runtime/vm/source_report.cc
index 03eaf12..f999c81 100644
--- a/runtime/vm/source_report.cc
+++ b/runtime/vm/source_report.cc
@@ -59,7 +59,7 @@
// Build the profile.
SampleFilter samplesForIsolate(thread_->isolate()->main_port(),
Thread::kMutatorTask, -1, -1);
- profile_.Build(thread, &samplesForIsolate, Profiler::sample_buffer());
+ profile_.Build(thread, &samplesForIsolate, Profiler::sample_block_buffer());
}
}
diff --git a/runtime/vm/thread.cc b/runtime/vm/thread.cc
index d03ff5a..09a3b25 100644
--- a/runtime/vm/thread.cc
+++ b/runtime/vm/thread.cc
@@ -448,6 +448,15 @@
}
heap()->CollectGarbage(Heap::kNew);
}
+
+#if !defined(PRODUCT)
+ // Processes completed SampleBlocks and sends CPU sample events over the
+ // service protocol when applicable.
+ SampleBlockBuffer* sample_buffer = Profiler::sample_block_buffer();
+ if (sample_buffer != nullptr && sample_buffer->process_blocks()) {
+ sample_buffer->ProcessCompletedBlocks();
+ }
+#endif // !defined(PRODUCT)
}
if ((interrupt_bits & kMessageInterrupt) != 0) {
MessageHandler::MessageStatus status =
diff --git a/sdk/lib/_internal/js_dev_runtime/patch/js_patch.dart b/sdk/lib/_internal/js_dev_runtime/patch/js_patch.dart
index 033b303..feb5ef1 100644
--- a/sdk/lib/_internal/js_dev_runtime/patch/js_patch.dart
+++ b/sdk/lib/_internal/js_dev_runtime/patch/js_patch.dart
@@ -9,7 +9,6 @@
import 'dart:_js_helper' show patch, NoReifyGeneric, Primitives;
import 'dart:_foreign_helper' show JS;
-import 'dart:_interceptors' show JavaScriptObject;
import 'dart:_runtime' as dart;
@patch
@@ -368,7 +367,7 @@
int ms = JS('!', '#.getTime()', o);
return DateTime.fromMillisecondsSinceEpoch(ms);
} else if (o is _DartObject &&
- !identical(dart.getReifiedType(o), dart.typeRep<JavaScriptObject>())) {
+ !identical(dart.getReifiedType(o), dart.jsobject)) {
return o._dartObj;
} else {
return _wrapToDart(o);
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/rtti.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/rtti.dart
index 9e99b26..1f362a8 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/rtti.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/rtti.dart
@@ -92,13 +92,13 @@
return JS('', '#.constructor', obj);
}
var result = JS('', '#[#]', obj, _extensionType);
- if (result == null) return typeRep<JavaScriptObject>();
+ if (result == null) return JS('', '#', jsobject);
return result;
case "function":
// All Dart functions and callable classes must set _runtimeType
var result = JS('', '#[#]', obj, _runtimeType);
if (result != null) return result;
- return typeRep<JavaScriptObject>();
+ return JS('', '#', jsobject);
case "undefined":
return JS('', '#', Null);
case "number":
@@ -109,7 +109,7 @@
return JS('', '#', String);
case "symbol":
default:
- return typeRep<JavaScriptObject>();
+ return JS('', '#', jsobject);
}
}
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart
index 2cd212a..e4afbf4 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart
@@ -10,8 +10,7 @@
import 'dart:_debugger' show stackTraceMapper, trackCall;
import 'dart:_foreign_helper' show JS, JSExportName, rest, spread;
-import 'dart:_interceptors'
- show JSArray, jsNull, JSFunction, NativeError, JavaScriptObject;
+import 'dart:_interceptors' show JSArray, jsNull, JSFunction, NativeError;
import 'dart:_internal' as internal show LateError, Symbol;
import 'dart:_js_helper'
show
diff --git a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart
index e48e530..7874940 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart
@@ -154,8 +154,7 @@
}
@notNull
-bool _isJsObject(obj) =>
- JS('!', '# === #', getReifiedType(obj), typeRep<JavaScriptObject>());
+bool _isJsObject(obj) => JS('!', '# === #', getReifiedType(obj), jsobject);
/// Asserts that [f] is a native JS functions and returns it if so.
///
@@ -238,7 +237,7 @@
return raw;
}
- Object rawJSTypeForCheck() => _getRawJSType() ?? typeRep<JavaScriptObject>();
+ Object rawJSTypeForCheck() => _getRawJSType() ?? jsobject;
@notNull
@JSExportName('is')
@@ -479,6 +478,12 @@
final bottom = unwrapType(Null);
+class JSObjectType extends DartType {
+ toString() => 'NativeJavaScriptObject';
+}
+
+final jsobject = JSObjectType();
+
/// Dev Compiler's implementation of Type, wrapping its internal [_type].
class _Type extends Type {
/// The internal type representation, either a [DartType] or class constructor
@@ -1513,43 +1518,8 @@
return ${_equalType(t2, Function)};
}
- // Even though lazy and anonymous JS types are natural subtypes of
- // JavaScriptObject, JS types should be treated as mutual subtypes of each
- // other. This allows users to be able to interface with both extension
- // types on JavaScriptObject and package:js using the same object.
- //
- // Therefore, the following relationships hold true:
- //
- // JavaScriptObject <: package:js types
- // package:js types <: JavaScriptObject
- //
- // TODO: This doesn't allow package:js type <: another package:js type yet.
-
- if (${_isInterfaceSubtype(t1, JavaScriptObject, strictMode)}
- && (${_jsInstanceOf(t2, LazyJSType)}
- || ${_jsInstanceOf(t2, AnonymousJSType)}
- // TODO: Since package:js types are instances of LazyJSType and
- // AnonymousJSType, we don't have a mechanism to determine if *some*
- // package:js type implements t2. This will possibly require keeping
- // a map of these relationships for this subtyping check. For now,
- // don't execute the following checks.
- // || _isInterfaceSubtype(LazyJSType, t2, strictMode)
- // || _isInterfaceSubtype(AnonymousJSType, t2, strictMode)
- )) {
- return true;
- }
-
- if (${_isInterfaceSubtype(JavaScriptObject, t2, strictMode)}
- && (${_jsInstanceOf(t1, LazyJSType)}
- || ${_jsInstanceOf(t1, AnonymousJSType)}
- // TODO: We don't have a check in `isInterfaceSubtype` to check that
- // a class is a subtype of *some* package:js class. This will likely
- // require modifying `_isInterfaceSubtype` to check if the
- // interfaces of t1 include a package:js type. For now, don't
- // execute the following checks.
- // || _isInterfaceSubtype(t1, LazyJSType, strictMode)
- // || _isInterfaceSubtype(t1, AnonymousJSType, strictMode)
- )) {
+ // All JS types are subtypes of anonymous JS types.
+ if ($t1 === $jsobject && ${_jsInstanceOf(t2, AnonymousJSType)}) {
return true;
}
diff --git a/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart b/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart
index fdc771d..71c559c 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart
@@ -74,40 +74,45 @@
operator []=(int index, E value);
}
-/// The interface implemented by JavaScript objects.
-///
-/// These are methods in addition to the regular Dart Object methods like
-/// [Object.hashCode]. This is the type that should be exported by a JavaScript
-/// interop library.
+/**
+ * The interface implemented by JavaScript objects. These are methods in
+ * addition to the regular Dart Object methods like [Object.hashCode].
+ *
+ * This is the type that should be exported by a JavaScript interop library.
+ */
abstract class JSObject {}
-/// Interceptor base class for JavaScript objects not recognized as some more
-/// specific native type.
-
-/// Unlike dart2js, ddc does not intercept JS objects, so this is only used as
-/// an on-type for JS interop extension types. All JS interop objects should be
-/// castable to this type.
+/**
+ * Interceptor base class for JavaScript objects not recognized as some more
+ * specific native type.
+ */
abstract class JavaScriptObject extends Interceptor implements JSObject {
const JavaScriptObject();
+
+ // It would be impolite to stash a property on the object.
+ int get hashCode => 0;
+
+ Type get runtimeType => JSObject;
}
-/// Interceptor for plain JavaScript objects created as JavaScript object
-/// literals or `new Object()`.
-///
-/// Note that this isn't being used today in ddc. Instead of using interceptors,
-/// we have other type logic to distinguish JS types.
+/**
+ * Interceptor for plain JavaScript objects created as JavaScript object
+ * literals or `new Object()`.
+ */
class PlainJavaScriptObject extends JavaScriptObject {
const PlainJavaScriptObject();
}
-/// Interceptor for unclassified JavaScript objects, typically objects with a
-/// non-trivial prototype chain.
-///
-/// This class also serves as a fallback for unknown JavaScript exceptions.
-/// Note that this isn't being used today in ddc. Instead of using interceptors,
-/// we have other type logic to distinguish JS types.
+/**
+ * Interceptor for unclassified JavaScript objects, typically objects with a
+ * non-trivial prototype chain.
+ *
+ * This class also serves as a fallback for unknown JavaScript exceptions.
+ */
class UnknownJavaScriptObject extends JavaScriptObject {
const UnknownJavaScriptObject();
+
+ String toString() => JS<String>('!', 'String(#)', this);
}
class NativeError extends Interceptor {
diff --git a/sdk/lib/_internal/js_runtime/lib/constant_map.dart b/sdk/lib/_internal/js_runtime/lib/constant_map.dart
index d5a5174..c7a2d84 100644
--- a/sdk/lib/_internal/js_runtime/lib/constant_map.dart
+++ b/sdk/lib/_internal/js_runtime/lib/constant_map.dart
@@ -12,37 +12,24 @@
abstract class ConstantMap<K, V> implements Map<K, V> {
// Used to create unmodifiable maps from other maps.
factory ConstantMap.from(Map other) {
- var keys = new List<K>.from(other.keys);
+ final keys = List<K>.from(other.keys);
bool allStrings = true;
for (var k in keys) {
- if (k is! String) {
+ if (k is! String || '__proto__' == k) {
allStrings = false;
break;
}
}
if (allStrings) {
- bool containsProto = false;
- var protoValue = null;
var object = JS('=Object', '{}');
- int length = 0;
- for (var k in keys) {
+ for (final k in keys) {
V v = other[k];
- if (k != '__proto__') {
- if (!jsHasOwnProperty(object, k as String)) length++;
- JS('void', '#[#] = #', object, k, v);
- } else {
- containsProto = true;
- protoValue = v;
- }
+ JS('void', '#[#] = #', object, k, v);
}
- if (containsProto) {
- length++;
- return new ConstantProtoMap<K, V>._(length, object, keys, protoValue);
- }
- return new ConstantStringMap<K, V>._(length, object, keys);
+ return ConstantStringMap<K, V>._(keys.length, object, keys);
}
// TODO(lrn): Make a proper unmodifiable map implementation.
- return new ConstantMapView<K, V>(new Map.from(other));
+ return ConstantMapView<K, V>(Map.from(other));
}
const ConstantMap._();
@@ -55,7 +42,7 @@
String toString() => MapBase.mapToString(this);
static Never _throwUnmodifiable() {
- throw new UnsupportedError('Cannot modify unmodifiable Map');
+ throw UnsupportedError('Cannot modify unmodifiable Map');
}
void operator []=(K key, V val) {
@@ -70,14 +57,19 @@
_throwUnmodifiable();
}
- void clear() => _throwUnmodifiable();
- void addAll(Map<K, V> other) => _throwUnmodifiable();
+ void clear() {
+ _throwUnmodifiable();
+ }
+
+ void addAll(Map<K, V> other) {
+ _throwUnmodifiable();
+ }
Iterable<MapEntry<K, V>> get entries sync* {
// `this[key]` has static type `V?` but is always `V`. Rather than `as V`,
// we use `as dynamic` so the upcast requires no checking and the implicit
// downcast to `V` will be discarded in production.
- for (var key in keys) yield new MapEntry<K, V>(key, this[key] as dynamic);
+ for (var key in keys) yield MapEntry<K, V>(key, this[key] as dynamic);
}
void addEntries(Iterable<MapEntry<K, V>> entries) {
@@ -151,32 +143,14 @@
}
Iterable<K> get keys {
- return new _ConstantMapKeyIterable<K>(this);
+ return _ConstantMapKeyIterable<K>(this);
}
Iterable<V> get values {
- return new MappedIterable<K, V>(_keysArray, (key) => _fetch(key));
+ return MappedIterable<K, V>(_keysArray, (key) => _fetch(key));
}
}
-class ConstantProtoMap<K, V> extends ConstantStringMap<K, V> {
- // This constructor is not used. The instantiation is shortcut by the
- // compiler. It is here to make the uninitialized final fields legal.
- ConstantProtoMap._(length, jsObject, keys, this._protoValue)
- : super._(length, jsObject, keys);
-
- final V _protoValue;
-
- bool containsKey(Object? key) {
- if (key is! String) return false;
- if ('__proto__' == key) return true;
- return jsHasOwnProperty(_jsObject, key);
- }
-
- _fetch(key) =>
- '__proto__' == key ? _protoValue : jsPropertyAccess(_jsObject, key);
-}
-
class _ConstantMapKeyIterable<K> extends Iterable<K> {
ConstantStringMap<K, dynamic> _map;
_ConstantMapKeyIterable(this._map);
@@ -199,7 +173,7 @@
Map<K, V> _getMap() {
LinkedHashMap<K, V>? backingMap = JS('LinkedHashMap|Null', r'#.$map', this);
if (backingMap == null) {
- backingMap = new JsLinkedHashMap<K, V>();
+ backingMap = JsLinkedHashMap<K, V>();
fillLiteralMap(_jsData, backingMap);
JS('', r'#.$map = #', this, backingMap);
}
diff --git a/tools/VERSION b/tools/VERSION
index db6c054..4e0fc8a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 14
PATCH 0
-PRERELEASE 328
+PRERELEASE 329
PRERELEASE_PATCH 0
\ No newline at end of file