Version 2.10.0-109.0.dev

Merge commit '9181df9d6bf5b3d80e59d0f249c8ff9460b0f28f' into 'dev'
diff --git a/pkg/analysis_server/test/analysis/get_navigation_test.dart b/pkg/analysis_server/test/analysis/get_navigation_test.dart
index f2f2cf1..4dbe557 100644
--- a/pkg/analysis_server/test/analysis/get_navigation_test.dart
+++ b/pkg/analysis_server/test/analysis/get_navigation_test.dart
@@ -43,6 +43,29 @@
     assertHasTarget('test = 0');
   }
 
+  Future<void> test_comment_outsideReference() async {
+    addTestFile('''
+/// Returns a [String].
+String main() {
+}''');
+    await waitForTasksFinished();
+    var search = 'Returns';
+    await _getNavigation(testFile, testCode.indexOf(search), 1);
+    expect(regions, hasLength(0));
+  }
+
+  Future<void> test_comment_reference() async {
+    addTestFile('''
+/// Returns a [String].
+String main() {
+}''');
+    await waitForTasksFinished();
+    var search = '[String';
+    await _getNavigation(testFile, testCode.indexOf(search), 1);
+    expect(regions, hasLength(1));
+    assertHasRegion('String]');
+  }
+
   Future<void> test_fieldType() async {
     // This test mirrors test_navigation() from
     // test/integration/analysis/get_navigation_test.dart
@@ -101,20 +124,6 @@
     expect(testTargets[0].kind, ElementKind.LIBRARY);
   }
 
-  Future<void> test_importKeyword() async {
-    addTestFile('''
-import 'dart:math';
-
-main() {
-}''');
-    await waitForTasksFinished();
-    await _getNavigation(testFile, 0, 1);
-    expect(regions, hasLength(1));
-    assertHasRegionString("'dart:math'");
-    expect(testTargets, hasLength(1));
-    expect(testTargets[0].kind, ElementKind.LIBRARY);
-  }
-
   Future<void> test_importUri() async {
     addTestFile('''
 import 'dart:math';
diff --git a/pkg/analysis_server/test/integration/analysis/get_navigation_test.dart b/pkg/analysis_server/test/integration/analysis/get_navigation_test.dart
index bd69280..1288adb 100644
--- a/pkg/analysis_server/test/integration/analysis/get_navigation_test.dart
+++ b/pkg/analysis_server/test/integration/analysis/get_navigation_test.dart
@@ -41,9 +41,7 @@
     expect(target.startColumn, 7);
   }
 
-  @failingTest
   Future<void> test_navigation_no_result() async {
-    // This fails - it returns navigation results for a whitespace area (#28799).
     var pathname = sourcePath('test.dart');
     var text = r'''
 //
diff --git a/pkg/analysis_server/test/lsp/definition_test.dart b/pkg/analysis_server/test/lsp/definition_test.dart
index a243579..d64412c 100644
--- a/pkg/analysis_server/test/lsp/definition_test.dart
+++ b/pkg/analysis_server/test/lsp/definition_test.dart
@@ -55,6 +55,26 @@
     expect(loc.uri, equals(referencedFileUri.toString()));
   }
 
+  Future<void> test_comment_adjacentReference() async {
+    /// Computing Dart navigation locates a node at the provided offset then
+    /// returns all navigation regions inside it. This test ensures we filter
+    /// out any regions that are in the same target node (the comment) but do
+    /// not span the requested offset.
+    final contents = '''
+    /// Te^st
+    ///
+    /// References [String].
+    main() {}
+    ''';
+
+    await initialize();
+    await openFile(mainFileUri, withoutMarkers(contents));
+    final res = await getDefinitionAsLocation(
+        mainFileUri, positionFromMarker(contents));
+
+    expect(res, hasLength(0));
+  }
+
   Future<void> test_fromPlugins() async {
     final pluginAnalyzedFilePath = join(projectFolderPath, 'lib', 'foo.foo');
     final pluginAnalyzedFileUri = Uri.file(pluginAnalyzedFilePath);
@@ -176,6 +196,59 @@
     expect(res, isEmpty);
   }
 
+  /// Failing due to incorrect range because _DartNavigationCollector._getCodeLocation
+  /// does not handle parts.
+  @failingTest
+  Future<void> test_part() async {
+    final mainContents = '''
+    import 'lib.dart';
+
+    main() {
+      Icons.[[ad^d]]();
+    }
+    ''';
+
+    final libContents = '''
+    part 'part.dart';
+    ''';
+
+    final partContents = '''
+    part of 'lib.dart';
+
+    void unrelatedFunction() {}
+
+    class Icons {
+      /// `targetRange` should not include the dartDoc but should include the full
+      /// function body. `targetSelectionRange` will be just the name.
+      [[String add = "Test"]];
+    }
+
+    void otherUnrelatedFunction() {}
+    ''';
+
+    final libFileUri = Uri.file(join(projectFolderPath, 'lib', 'lib.dart'));
+    final partFileUri = Uri.file(join(projectFolderPath, 'lib', 'part.dart'));
+
+    await initialize(
+        textDocumentCapabilities:
+            withLocationLinkSupport(emptyTextDocumentClientCapabilities));
+    await openFile(mainFileUri, withoutMarkers(mainContents));
+    await openFile(libFileUri, withoutMarkers(libContents));
+    await openFile(partFileUri, withoutMarkers(partContents));
+    final res = await getDefinitionAsLocationLinks(
+        mainFileUri, positionFromMarker(mainContents));
+
+    expect(res, hasLength(1));
+    var loc = res.single;
+    expect(loc.originSelectionRange, equals(rangeFromMarkers(mainContents)));
+    expect(loc.targetUri, equals(partFileUri.toString()));
+    expect(loc.targetRange, equals(rangeFromMarkers(partContents)));
+    expect(
+      loc.targetSelectionRange,
+      equals(rangeOfString(partContents, 'add')),
+    );
+  }
+
   Future<void> test_sameLine() async {
     final contents = '''
     int plusOne(int [[value]]) => 1 + val^ue;
diff --git a/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart b/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
index a0b22fb..4fd07d6 100644
--- a/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/assignment_expression_resolver.dart
@@ -100,27 +100,6 @@
     _flowAnalysis?.assignmentExpression_afterRight(node);
   }
 
-  /// Set the static type of [node] to be the least upper bound of the static
-  /// types [staticType1] and [staticType2].
-  ///
-  /// TODO(scheglov) this is duplicate
-  void _analyzeLeastUpperBoundTypes(
-      Expression node, DartType staticType1, DartType staticType2) {
-    // TODO(brianwilkerson) Determine whether this can still happen.
-    staticType1 ??= DynamicTypeImpl.instance;
-
-    // TODO(brianwilkerson) Determine whether this can still happen.
-    staticType2 ??= DynamicTypeImpl.instance;
-
-    DartType staticType =
-        _typeSystem.getLeastUpperBound(staticType1, staticType2) ??
-            DynamicTypeImpl.instance;
-
-    staticType = _resolver.toLegacyTypeIfOptOut(staticType);
-
-    _inferenceHelper.recordStaticType(node, staticType);
-  }
-
   void _checkForInvalidAssignment(
     DartType writeType,
     Expression right,
@@ -170,17 +149,6 @@
     return true;
   }
 
-  /// Return the non-nullable variant of the [type] if null safety is enabled,
-  /// otherwise return the type itself.
-  ///
-  // TODO(scheglov) this is duplicate
-  DartType _nonNullable(DartType type) {
-    if (_isNonNullableByDefault) {
-      return _typeSystem.promoteToNonNull(type);
-    }
-    return type;
-  }
-
   /// Record that the static type of the given node is the given type.
   ///
   /// @param expression the node whose type is to be recorded
@@ -348,29 +316,20 @@
       var rightType = node.rightHandSide.staticType;
       _inferenceHelper.recordStaticType(node, rightType);
     } else if (operator == TokenType.QUESTION_QUESTION_EQ) {
+      var leftType = node.readType;
+
+      // The LHS value will be used only if it is non-null.
       if (_isNonNullableByDefault) {
-        // The static type of a compound assignment using ??= with NNBD is the
-        // least upper bound of the static types of the LHS and RHS after
-        // promoting the LHS/ to non-null (as we know its value will not be used
-        // if null)
-        _analyzeLeastUpperBoundTypes(
-          node,
-          _typeSystem.promoteToNonNull(node.readType),
-          getReadType(node.rightHandSide),
-        );
-      } else {
-        // The static type of a compound assignment using ??= before NNBD is the
-        // least upper bound of the static types of the LHS and RHS.
-        _analyzeLeastUpperBoundTypes(
-          node,
-          node.readType,
-          node.rightHandSide.staticType,
-        );
+        leftType = _typeSystem.promoteToNonNull(leftType);
       }
+
+      var rightType = node.rightHandSide.staticType;
+      var result = _typeSystem.getLeastUpperBound(leftType, rightType);
+
+      _inferenceHelper.recordStaticType(node, result);
     } else if (operator == TokenType.AMPERSAND_AMPERSAND_EQ ||
         operator == TokenType.BAR_BAR_EQ) {
-      _inferenceHelper.recordStaticType(
-          node, _nonNullable(_typeProvider.boolType));
+      _inferenceHelper.recordStaticType(node, _typeProvider.boolType);
     } else {
       var rightType = node.rightHandSide.staticType;
 
diff --git a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
index efa84ae..cd05675 100644
--- a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
+++ b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
@@ -19,7 +19,7 @@
     CompilationUnit unit,
     int offset,
     int length) {
-  var dartCollector = _DartNavigationCollector(collector);
+  var dartCollector = _DartNavigationCollector(collector, offset, length);
   var visitor = _DartNavigationComputerVisitor(resourceProvider, dartCollector);
   if (offset == null || length == null) {
     unit.accept(visitor);
@@ -43,8 +43,11 @@
 /// A Dart specific wrapper around [NavigationCollector].
 class _DartNavigationCollector {
   final NavigationCollector collector;
+  final int requestedOffset;
+  final int requestedLength;
 
-  _DartNavigationCollector(this.collector);
+  _DartNavigationCollector(
+      this.collector, this.requestedOffset, this.requestedLength);
 
   void _addRegion(int offset, int length, Element element) {
     if (element is FieldFormalParameterElement) {
@@ -56,6 +59,12 @@
     if (element.location == null) {
       return;
     }
+    // Discard elements that don't span the offset/range given (if provided).
+    if (requestedOffset != null &&
+        (offset > requestedOffset + (requestedLength ?? 0) ||
+            offset + length < requestedOffset)) {
+      return;
+    }
     var converter = AnalyzerConverter();
     var kind = converter.convertElementKind(element.kind);
     var location = converter.locationFromElement(element);
@@ -118,9 +127,13 @@
     }
 
     // Read the declaration so we can get the offset after the doc comments.
-    final declaration = codeElement.session
-        .getParsedLibrary(location.file)
-        .getElementDeclaration(codeElement);
+    // TODO(dantup): Skip this for parts (getParsedLibrary will throw), but find
+    // a better solution.
+    final declaration = !codeElement.session.getFile(location.file).isPart
+        ? codeElement.session
+            .getParsedLibrary(location.file)
+            .getElementDeclaration(codeElement)
+        : null;
     var node = declaration?.node;
     if (node is VariableDeclaration) {
       node = node.parent;
diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart
index d60260f..41f0901 100644
--- a/pkg/dartdev/lib/dartdev.dart
+++ b/pkg/dartdev/lib/dartdev.dart
@@ -22,6 +22,7 @@
 import 'src/commands/run.dart';
 import 'src/commands/test.dart';
 import 'src/core.dart';
+import 'src/events.dart';
 import 'src/experiments.dart';
 import 'src/sdk.dart';
 import 'src/utils.dart';
@@ -35,10 +36,6 @@
   final stopwatch = Stopwatch();
   int result;
 
-  // The Analytics instance used to report information back to Google Analytics,
-  // see lib/src/analytics.dart.
-  Analytics analytics;
-
   // The exit code for the dartdev process, null indicates that it has not yet
   // been set yet. The value is set in the catch and finally blocks below.
   int exitCode;
@@ -47,7 +44,9 @@
   Object exception;
   StackTrace stackTrace;
 
-  analytics =
+  // The Analytics instance used to report information back to Google Analytics,
+  // see lib/src/analytics.dart.
+  Analytics analytics =
       createAnalyticsInstance(args.contains('--disable-dartdev-analytics'));
 
   // If we have not printed the analyticsNoticeOnFirstRunMessage to stdout,
@@ -92,11 +91,13 @@
     // RunCommand.ddsHost = ddsUrl[0];
     // RunCommand.ddsPort = ddsUrl[1];
   }
+
   String commandName;
 
   try {
     stopwatch.start();
     final runner = DartdevRunner(args);
+
     // Run can't be called with the '--disable-dartdev-analytics' flag, remove
     // it if it is contained in args.
     if (args.contains('--disable-dartdev-analytics')) {
@@ -119,14 +120,6 @@
         )
         .toList();
 
-    // Before calling to run, send the first ping to analytics to have the first
-    // ping, as well as the command itself, running in parallel.
-    if (analytics.enabled) {
-      commandName = getCommandStr(args, runner.commands.keys.toList());
-      // ignore: unawaited_futures
-      analytics.sendEvent(eventCategory, commandName);
-    }
-
     // If ... help pub ... is in the args list, remove 'help', and add '--help'
     // to the end of the list.  This will make it possible to use the help
     // command to access subcommands of pub such as `dart help pub publish`, see
@@ -135,6 +128,17 @@
       args = PubUtils.modifyArgs(args);
     }
 
+    // For the commands format and migrate, dartdev itself sends the
+    // sendScreenView notification to analytics, for all other
+    // dartdev commands (instances of DartdevCommand) the commands send this
+    // to analytics.
+    commandName = ArgParserUtils.getCommandStr(args);
+    if (analytics.enabled &&
+        (commandName == formatCmdName || commandName == migrateCmdName)) {
+      // ignore: unawaited_futures
+      analytics.sendScreenView(commandName);
+    }
+
     // Finally, call the runner to execute the command, see DartdevRunner.
     result = await runner.run(args);
   } catch (e, st) {
@@ -157,7 +161,22 @@
 
     // Send analytics before exiting
     if (analytics.enabled) {
-      analytics.setSessionValue(exitCodeParam, exitCode);
+      // For commands that are not DartdevCommand instances, we manually create
+      // and send the UsageEvent from here:
+      if (commandName == formatCmdName) {
+        // ignore: unawaited_futures
+        FormatUsageEvent(
+          exitCode: exitCode,
+          args: args,
+        ).send(analyticsInstance);
+      } else if (commandName == migrateCmdName) {
+        // ignore: unawaited_futures
+        MigrateUsageEvent(
+          exitCode: exitCode,
+          args: args,
+        ).send(analyticsInstance);
+      }
+
       // ignore: unawaited_futures
       analytics.sendTiming(commandName, stopwatch.elapsedMilliseconds,
           category: 'commands');
diff --git a/pkg/dartdev/lib/src/analytics.dart b/pkg/dartdev/lib/src/analytics.dart
index 5a1037a..595fec0 100644
--- a/pkg/dartdev/lib/src/analytics.dart
+++ b/pkg/dartdev/lib/src/analytics.dart
@@ -26,7 +26,6 @@
   ║ `dart --enable-analytics`                                                  ║
   ╚════════════════════════════════════════════════════════════════════════════╝
 ''';
-const String _unknownCommand = '<unknown>';
 const String _appName = 'dartdev';
 const String _dartDirectoryName = '.dart';
 const String _settingsFileName = 'dartdev.json';
@@ -40,13 +39,15 @@
 const String eventCategory = 'dartdev';
 const String exitCodeParam = 'exitCode';
 
-Analytics instance;
+Analytics _instance;
+
+Analytics get analyticsInstance => _instance;
 
 /// Create and return an [Analytics] instance, this value is cached and returned
 /// on subsequent calls.
 Analytics createAnalyticsInstance(bool disableAnalytics) {
-  if (instance != null) {
-    return instance;
+  if (_instance != null) {
+    return _instance;
   }
 
   // Dartdev tests pass a hidden 'disable-dartdev-analytics' flag which is
@@ -54,16 +55,16 @@
   // Also, stdout.hasTerminal is checked, if there is no terminal we infer that
   // a machine is running dartdev so we return analytics shouldn't be set.
   if (disableAnalytics) {
-    instance = DisabledAnalytics(_trackingId, _appName);
-    return instance;
+    _instance = DisabledAnalytics(_trackingId, _appName);
+    return _instance;
   }
 
   var settingsDir = getDartStorageDirectory();
   if (settingsDir == null) {
     // Some systems don't support user home directories; for those, fail
     // gracefully by returning a disabled analytics object.
-    instance = DisabledAnalytics(_trackingId, _appName);
-    return instance;
+    _instance = DisabledAnalytics(_trackingId, _appName);
+    return _instance;
   }
 
   if (!settingsDir.existsSync()) {
@@ -72,8 +73,8 @@
     } catch (e) {
       // If we can't create the directory for the analytics settings, fail
       // gracefully by returning a disabled analytics object.
-      instance = DisabledAnalytics(_trackingId, _appName);
-      return instance;
+      _instance = DisabledAnalytics(_trackingId, _appName);
+      return _instance;
     }
   }
 
@@ -85,22 +86,8 @@
   }
 
   var settingsFile = File(path.join(settingsDir.path, _settingsFileName));
-  instance = DartdevAnalytics(_trackingId, settingsFile, _appName);
-  return instance;
-}
-
-/// Return the first member from [args] that occurs in [allCommands], otherwise
-/// '<unknown>' is returned.
-///
-/// 'help' is special cased to have 'dart analyze help', 'dart help analyze',
-/// and 'dart analyze --help' all be recorded as a call to 'help' instead of
-/// 'help' and 'analyze'.
-String getCommandStr(List<String> args, List<String> allCommands) {
-  if (args.contains('help') || args.contains('-h') || args.contains('--help')) {
-    return 'help';
-  }
-  return args.firstWhere((arg) => allCommands.contains(arg),
-      orElse: () => _unknownCommand);
+  _instance = DartdevAnalytics(_trackingId, settingsFile, _appName);
+  return _instance;
 }
 
 /// The directory used to store the analytics settings file.
diff --git a/pkg/dartdev/lib/src/commands/analyze.dart b/pkg/dartdev/lib/src/commands/analyze.dart
index 139d262..08d8590 100644
--- a/pkg/dartdev/lib/src/commands/analyze.dart
+++ b/pkg/dartdev/lib/src/commands/analyze.dart
@@ -5,14 +5,18 @@
 import 'dart:async';
 import 'dart:io';
 
+import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 
 import '../core.dart';
+import '../events.dart';
 import '../sdk.dart';
 import '../utils.dart';
 import 'analyze_impl.dart';
 
 class AnalyzeCommand extends DartdevCommand<int> {
+  static const String cmdName = 'analyze';
+
   /// The maximum length of any of the existing severity labels.
   static const int _severityWidth = 7;
 
@@ -20,7 +24,7 @@
   /// message. The width left for the severity label plus the separator width.
   static const int _bodyIndentWidth = _severityWidth + 3;
 
-  AnalyzeCommand() : super('analyze', "Analyze the project's Dart code.") {
+  AnalyzeCommand() : super(cmdName, "Analyze the project's Dart code.") {
     argParser
       ..addFlag('fatal-infos',
           help: 'Treat info level issues as fatal.', negatable: false)
@@ -32,7 +36,7 @@
   String get invocation => '${super.invocation} [<directory>]';
 
   @override
-  FutureOr<int> run() async {
+  FutureOr<int> runImpl() async {
     if (argResults.rest.length > 1) {
       usageException('Only one directory is expected.');
     }
@@ -153,4 +157,19 @@
       return 0;
     }
   }
+
+  @override
+  UsageEvent createUsageEvent(int exitCode) => AnalyzeUsageEvent(
+        usagePath,
+        exitCode: exitCode,
+        args: argResults.arguments,
+      );
+}
+
+/// The [UsageEvent] for the analyze command.
+class AnalyzeUsageEvent extends UsageEvent {
+  AnalyzeUsageEvent(String usagePath,
+      {String label, @required int exitCode, @required List<String> args})
+      : super(AnalyzeCommand.cmdName, usagePath,
+            label: label, args: args, exitCode: exitCode);
 }
diff --git a/pkg/dartdev/lib/src/commands/compile.dart b/pkg/dartdev/lib/src/commands/compile.dart
index 5da7168..491d7ae 100644
--- a/pkg/dartdev/lib/src/commands/compile.dart
+++ b/pkg/dartdev/lib/src/commands/compile.dart
@@ -6,9 +6,11 @@
 import 'dart:io';
 
 import 'package:dart2native/generate.dart';
+import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 
 import '../core.dart';
+import '../events.dart';
 import '../sdk.dart';
 import '../vm_interop_handler.dart';
 
@@ -39,12 +41,13 @@
     stderr.flush();
     return false;
   }
-
   return true;
 }
 
-class CompileJSCommand extends DartdevCommand<int> {
-  CompileJSCommand() : super('js', 'Compile Dart to JavaScript.') {
+class CompileJSCommand extends CompileSubcommandCommand {
+  static const String cmdName = 'js';
+
+  CompileJSCommand() : super(cmdName, 'Compile Dart to JavaScript.') {
     argParser
       ..addOption(
         commonOptions['outputFile'].flag,
@@ -63,7 +66,7 @@
   String get invocation => '${super.invocation} <dart entry point>';
 
   @override
-  FutureOr<int> run() async {
+  FutureOr<int> runImpl() async {
     if (!Sdk.checkArtifactExists(sdk.dart2jsSnapshot)) {
       return 255;
     }
@@ -97,7 +100,10 @@
   }
 }
 
-class CompileSnapshotCommand extends DartdevCommand<int> {
+class CompileSnapshotCommand extends CompileSubcommandCommand {
+  static const String jitSnapshotCmdName = 'jit-snapshot';
+  static const String kernelCmdName = 'kernel';
+
   final String commandName;
   final String help;
   final String fileExt;
@@ -121,7 +127,7 @@
   String get invocation => '${super.invocation} <dart entry point>';
 
   @override
-  FutureOr<int> run() async {
+  FutureOr<int> runImpl() async {
     // We expect a single rest argument; the dart entry point.
     if (argResults.rest.length != 1) {
       // This throws.
@@ -157,7 +163,10 @@
   }
 }
 
-class CompileNativeCommand extends DartdevCommand<int> {
+class CompileNativeCommand extends CompileSubcommandCommand {
+  static const String exeCmdName = 'exe';
+  static const String aotSnapshotCmdName = 'aot-snapshot';
+
   final String commandName;
   final String format;
   final String help;
@@ -194,7 +203,7 @@
   String get invocation => '${super.invocation} <dart entry point>';
 
   @override
-  FutureOr<int> run() async {
+  FutureOr<int> runImpl() async {
     if (!Sdk.checkArtifactExists(genKernel) ||
         !Sdk.checkArtifactExists(genSnapshot)) {
       return 255;
@@ -230,30 +239,64 @@
   }
 }
 
-class CompileCommand extends DartdevCommand {
-  CompileCommand() : super('compile', 'Compile Dart to various formats.') {
+abstract class CompileSubcommandCommand extends DartdevCommand<int> {
+  CompileSubcommandCommand(String name, String description,
+      {bool hidden = false})
+      : super(name, description, hidden: hidden);
+
+  @override
+  UsageEvent createUsageEvent(int exitCode) => CompileUsageEvent(
+        usagePath,
+        exitCode: exitCode,
+        args: argResults.arguments,
+      );
+}
+
+class CompileCommand extends DartdevCommand<int> {
+  static const String cmdName = 'compile';
+
+  CompileCommand() : super(cmdName, 'Compile Dart to various formats.') {
     addSubcommand(CompileJSCommand());
     addSubcommand(CompileSnapshotCommand(
-      commandName: 'jit-snapshot',
+      commandName: CompileSnapshotCommand.jitSnapshotCmdName,
       help: 'to a JIT snapshot.',
       fileExt: 'jit',
       formatName: 'app-jit',
     ));
     addSubcommand(CompileSnapshotCommand(
-      commandName: 'kernel',
+      commandName: CompileSnapshotCommand.kernelCmdName,
       help: 'to a kernel snapshot.',
       fileExt: 'dill',
       formatName: 'kernel',
     ));
     addSubcommand(CompileNativeCommand(
-      commandName: 'exe',
+      commandName: CompileNativeCommand.exeCmdName,
       help: 'to a self-contained executable.',
       format: 'exe',
     ));
     addSubcommand(CompileNativeCommand(
-      commandName: 'aot-snapshot',
+      commandName: CompileNativeCommand.aotSnapshotCmdName,
       help: 'to an AOT snapshot.',
       format: 'aot',
     ));
   }
+
+  @override
+  UsageEvent createUsageEvent(int exitCode) => null;
+
+  @override
+  FutureOr<int> runImpl() {
+    // do nothing, this command is never run
+    return 0;
+  }
+}
+
+/// The [UsageEvent] for all compile commands, we could have each compile
+/// event be its own class instance, but for the time being [usagePath] takes
+/// care of the only difference.
+class CompileUsageEvent extends UsageEvent {
+  CompileUsageEvent(String usagePath,
+      {String label, @required int exitCode, @required List<String> args})
+      : super(CompileCommand.cmdName, usagePath,
+            label: label, exitCode: exitCode, args: args);
 }
diff --git a/pkg/dartdev/lib/src/commands/create.dart b/pkg/dartdev/lib/src/commands/create.dart
index 4eda721..920de13 100644
--- a/pkg/dartdev/lib/src/commands/create.dart
+++ b/pkg/dartdev/lib/src/commands/create.dart
@@ -7,14 +7,18 @@
 import 'dart:io' as io;
 import 'dart:math' as math;
 
+import 'package:meta/meta.dart';
 import 'package:path/path.dart' as p;
 import 'package:stagehand/stagehand.dart' as stagehand;
 
 import '../core.dart';
+import '../events.dart';
 import '../sdk.dart';
 
 /// A command to create a new project from a set of templates.
-class CreateCommand extends DartdevCommand {
+class CreateCommand extends DartdevCommand<int> {
+  static const String cmdName = 'create';
+
   static String defaultTemplateId = 'console-simple';
 
   static List<String> legalTemplateIds = [
@@ -31,7 +35,7 @@
       stagehand.getGenerator(templateId);
 
   CreateCommand({bool verbose = false})
-      : super('create', 'Create a new project.') {
+      : super(cmdName, 'Create a new project.') {
     argParser.addOption(
       'template',
       allowed: legalTemplateIds,
@@ -60,7 +64,7 @@
   String get invocation => '${super.invocation} <directory>';
 
   @override
-  FutureOr<int> run() async {
+  FutureOr<int> runImpl() async {
     if (argResults['list-templates']) {
       log.stdout(_availableTemplatesJson());
       return 0;
@@ -139,6 +143,13 @@
   }
 
   @override
+  UsageEvent createUsageEvent(int exitCode) => CreateUsageEvent(
+        usagePath,
+        exitCode: exitCode,
+        args: argResults.arguments,
+      );
+
+  @override
   String get usageFooter {
     int width = legalTemplateIds.map((s) => s.length).reduce(math.max);
     String desc = generators.map((g) {
@@ -169,6 +180,14 @@
   }
 }
 
+/// The [UsageEvent] for the create command.
+class CreateUsageEvent extends UsageEvent {
+  CreateUsageEvent(String usagePath,
+      {String label, @required int exitCode, @required List<String> args})
+      : super(CreateCommand.cmdName, usagePath,
+            label: label, exitCode: exitCode, args: args);
+}
+
 class DirectoryGeneratorTarget extends stagehand.GeneratorTarget {
   final stagehand.Generator generator;
   final io.Directory dir;
diff --git a/pkg/dartdev/lib/src/commands/fix.dart b/pkg/dartdev/lib/src/commands/fix.dart
index 36a8579..338158e 100644
--- a/pkg/dartdev/lib/src/commands/fix.dart
+++ b/pkg/dartdev/lib/src/commands/fix.dart
@@ -6,19 +6,23 @@
 import 'dart:io';
 
 import 'package:analysis_server_client/protocol.dart' hide AnalysisError;
+import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 
 import '../core.dart';
+import '../events.dart';
 import '../sdk.dart';
 import '../utils.dart';
 import 'analyze_impl.dart';
 
-class FixCommand extends DartdevCommand {
+class FixCommand extends DartdevCommand<int> {
+  static const String cmdName = 'fix';
+
   // This command is hidden as its currently experimental.
-  FixCommand() : super('fix', 'Fix Dart source code.', hidden: true);
+  FixCommand() : super(cmdName, 'Fix Dart source code.', hidden: true);
 
   @override
-  FutureOr<int> run() async {
+  FutureOr<int> runImpl() async {
     log.stdout('\n*** The `fix` command is provisional and subject to change '
         'or removal in future releases. ***\n');
 
@@ -76,7 +80,21 @@
       }
       log.stdout('Done.');
     }
-
     return 0;
   }
+
+  @override
+  UsageEvent createUsageEvent(int exitCode) => FixUsageEvent(
+        usagePath,
+        exitCode: exitCode,
+        args: argResults.arguments,
+      );
+}
+
+/// The [UsageEvent] for the fix command.
+class FixUsageEvent extends UsageEvent {
+  FixUsageEvent(String usagePath,
+      {String label, @required int exitCode, @required List<String> args})
+      : super(FixCommand.cmdName, usagePath,
+            label: label, exitCode: exitCode, args: args);
 }
diff --git a/pkg/dartdev/lib/src/commands/pub.dart b/pkg/dartdev/lib/src/commands/pub.dart
index 6b40ebc..c30bc61 100644
--- a/pkg/dartdev/lib/src/commands/pub.dart
+++ b/pkg/dartdev/lib/src/commands/pub.dart
@@ -5,14 +5,37 @@
 import 'dart:async';
 
 import 'package:args/args.dart';
+import 'package:meta/meta.dart';
 
 import '../core.dart';
+import '../events.dart';
 import '../experiments.dart';
 import '../sdk.dart';
 import '../vm_interop_handler.dart';
 
 class PubCommand extends DartdevCommand<int> {
-  PubCommand() : super('pub', 'Work with packages.');
+  static const String cmdName = 'pub';
+
+  PubCommand() : super(cmdName, 'Work with packages.');
+
+  // TODO(jwren) as soon as pub commands are are implemented directly in
+  //  dartdev, remove this static list.
+  /// A list of all subcommands, used only for the implementation of
+  /// [usagePath], see below.
+  static List<String> pubSubcommands = [
+    'cache',
+    'deps',
+    'downgrade',
+    'get',
+    'global',
+    'logout',
+    'outdated',
+    'publish',
+    'run',
+    'upgrade',
+    'uploader',
+    'version',
+  ];
 
   @override
   ArgParser createArgParser() => ArgParser.allowAnything();
@@ -34,8 +57,26 @@
     VmInteropHandler.run(command, args);
   }
 
+  /// Since the pub subcommands are not subclasses of DartdevCommand, we
+  /// override [usagePath] here as a special case to cover the first subcommand
+  /// under pub, i.e. we will have the path pub/cache
   @override
-  FutureOr<int> run() async {
+  String get usagePath {
+    if (argResults == null) {
+      return name;
+    }
+    var args = argResults.arguments;
+    var cmdIndex = args.indexOf(name) ?? 0;
+    for (int i = cmdIndex + 1; i < args.length; i++) {
+      if (pubSubcommands.contains(args[i])) {
+        return '$name/${args[i]}';
+      }
+    }
+    return name;
+  }
+
+  @override
+  FutureOr<int> runImpl() async {
     if (!Sdk.checkArtifactExists(sdk.pubSnapshot)) {
       return 255;
     }
@@ -65,4 +106,26 @@
     VmInteropHandler.run(command, args);
     return 0;
   }
+
+  @override
+  UsageEvent createUsageEvent(int exitCode) => PubUsageEvent(
+        usagePath,
+        exitCode: exitCode,
+        specifiedExperiments: specifiedExperiments,
+        args: argResults.arguments,
+      );
+}
+
+/// The [UsageEvent] for the pub command.
+class PubUsageEvent extends UsageEvent {
+  PubUsageEvent(String usagePath,
+      {String label,
+      @required int exitCode,
+      @required List<String> specifiedExperiments,
+      @required List<String> args})
+      : super(PubCommand.cmdName, usagePath,
+            label: label,
+            exitCode: exitCode,
+            specifiedExperiments: specifiedExperiments,
+            args: args);
 }
diff --git a/pkg/dartdev/lib/src/commands/run.dart b/pkg/dartdev/lib/src/commands/run.dart
index feeb99c..9fa39e6 100644
--- a/pkg/dartdev/lib/src/commands/run.dart
+++ b/pkg/dartdev/lib/src/commands/run.dart
@@ -8,15 +8,19 @@
 import 'dart:io';
 
 import 'package:args/args.dart';
+import 'package:meta/meta.dart';
 import 'package:path/path.dart';
 
 import '../core.dart';
+import '../events.dart';
 import '../experiments.dart';
 import '../sdk.dart';
 import '../utils.dart';
 import '../vm_interop_handler.dart';
 
 class RunCommand extends DartdevCommand<int> {
+  static const String cmdName = 'run';
+
   static bool launchDds = false;
   static String ddsHost;
   static String ddsPort;
@@ -41,7 +45,7 @@
 
   RunCommand({this.verbose = false})
       : super(
-          'run',
+          cmdName,
           'Run a Dart program.',
         ) {
     // NOTE: When updating this list of flags, be sure to add any VM flags to
@@ -153,7 +157,7 @@
   String get invocation => '${super.invocation} <dart file | package target>';
 
   @override
-  FutureOr<int> run() async {
+  FutureOr<int> runImpl() async {
     // The command line arguments after 'run'
     var args = argResults.arguments.toList();
 
@@ -236,6 +240,14 @@
     VmInteropHandler.run(path, runArgs);
     return 0;
   }
+
+  @override
+  UsageEvent createUsageEvent(int exitCode) => RunUsageEvent(
+        usagePath,
+        exitCode: exitCode,
+        specifiedExperiments: specifiedExperiments,
+        args: argResults.arguments,
+      );
 }
 
 class _DebuggingSession {
@@ -282,3 +294,17 @@
     }
   }
 }
+
+/// The [UsageEvent] for the run command.
+class RunUsageEvent extends UsageEvent {
+  RunUsageEvent(String usagePath,
+      {String label,
+      @required int exitCode,
+      @required List<String> specifiedExperiments,
+      @required List<String> args})
+      : super(RunCommand.cmdName, usagePath,
+            label: label,
+            exitCode: exitCode,
+            specifiedExperiments: specifiedExperiments,
+            args: args);
+}
diff --git a/pkg/dartdev/lib/src/commands/test.dart b/pkg/dartdev/lib/src/commands/test.dart
index dcb5a7f..beba8c8 100644
--- a/pkg/dartdev/lib/src/commands/test.dart
+++ b/pkg/dartdev/lib/src/commands/test.dart
@@ -5,8 +5,10 @@
 import 'dart:async';
 
 import 'package:args/args.dart';
+import 'package:meta/meta.dart';
 
 import '../core.dart';
+import '../events.dart';
 import '../experiments.dart';
 import '../sdk.dart';
 import '../vm_interop_handler.dart';
@@ -15,7 +17,9 @@
 ///
 /// This command largely delegates to `pub run test`.
 class TestCommand extends DartdevCommand<int> {
-  TestCommand() : super('test', 'Run tests in this package.');
+  static const String cmdName = 'test';
+
+  TestCommand() : super(cmdName, 'Run tests in this package.');
 
   @override
   final ArgParser argParser = ArgParser.allowAnything();
@@ -26,7 +30,7 @@
   }
 
   @override
-  FutureOr<int> run() async {
+  FutureOr<int> runImpl() async {
     return _runImpl(argResults.arguments.toList());
   }
 
@@ -70,6 +74,14 @@
     return 0;
   }
 
+  @override
+  UsageEvent createUsageEvent(int exitCode) => TestUsageEvent(
+        usagePath,
+        exitCode: exitCode,
+        specifiedExperiments: specifiedExperiments,
+        args: argResults.arguments,
+      );
+
   void _printNoPubspecMessage(bool wasHelpCommand) {
     log.stdout('''
 No pubspec.yaml file found; please run this command from the root of your project.
@@ -118,6 +130,20 @@
   }
 }
 
+/// The [UsageEvent] for the test command.
+class TestUsageEvent extends UsageEvent {
+  TestUsageEvent(String usagePath,
+      {String label,
+      @required int exitCode,
+      @required List<String> specifiedExperiments,
+      @required List<String> args})
+      : super(TestCommand.cmdName, usagePath,
+            label: label,
+            exitCode: exitCode,
+            specifiedExperiments: specifiedExperiments,
+            args: args);
+}
+
 const String _terseHelp = 'Run tests in this package.';
 
 const String _usageHelp = 'Usage: dart test [files or directories...]';
diff --git a/pkg/dartdev/lib/src/core.dart b/pkg/dartdev/lib/src/core.dart
index cc3a505..5694415 100644
--- a/pkg/dartdev/lib/src/core.dart
+++ b/pkg/dartdev/lib/src/core.dart
@@ -2,6 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
 
@@ -10,6 +11,8 @@
 import 'package:cli_util/cli_logging.dart';
 import 'package:path/path.dart' as path;
 
+import 'analytics.dart';
+import 'events.dart';
 import 'experiments.dart';
 import 'sdk.dart';
 import 'utils.dart';
@@ -39,6 +42,55 @@
   @override
   ArgParser get argParser => _argParser ??= createArgParser();
 
+  /// This method should not be overridden by subclasses, instead classes should
+  /// override [runImpl] and [createUsageEvent]. If analytics is enabled by this
+  /// command and the user, a [sendScreenView] is called to analytics, and then
+  /// after the command is run, an event is sent to analytics.
+  ///
+  /// If analytics is not enabled by this command or the user, then [runImpl] is
+  /// called and the exitCode value is returned.
+  @override
+  FutureOr<int> run() async {
+    var path = usagePath;
+    if (path != null &&
+        analyticsInstance != null &&
+        analyticsInstance.enabled) {
+      // Send the screen view to analytics
+      // ignore: unawaited_futures
+      analyticsInstance.sendScreenView(path);
+
+      // Run this command
+      var exitCode = await runImpl();
+
+      // Send the event to analytics
+      // ignore: unawaited_futures
+      createUsageEvent(exitCode)?.send(analyticsInstance);
+
+      // Finally return the exit code
+      return exitCode;
+    } else {
+      // Analytics is not enabled, run the command and return the exit code
+      return runImpl();
+    }
+  }
+
+  UsageEvent createUsageEvent(int exitCode);
+
+  FutureOr<int> runImpl();
+
+  /// The command name path to send to Google Analytics. Return null to disable
+  /// tracking of the command.
+  String get usagePath {
+    if (parent is DartdevCommand) {
+      final commandParent = parent as DartdevCommand;
+      final parentPath = commandParent.usagePath;
+      // Don't report for parents that return null for usagePath.
+      return parentPath == null ? null : '$parentPath/$name';
+    } else {
+      return name;
+    }
+  }
+
   /// Create the ArgParser instance for this command.
   ///
   /// Subclasses can override this in order to create a customized ArgParser.
diff --git a/pkg/dartdev/lib/src/events.dart b/pkg/dartdev/lib/src/events.dart
new file mode 100644
index 0000000..6cc5547
--- /dev/null
+++ b/pkg/dartdev/lib/src/events.dart
@@ -0,0 +1,204 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:meta/meta.dart';
+import 'package:usage/usage.dart';
+
+import 'commands/analyze.dart';
+import 'commands/compile.dart';
+import 'commands/create.dart';
+import 'commands/fix.dart';
+import 'commands/pub.dart';
+import 'commands/run.dart';
+
+/// A list of all commands under dartdev.
+const List<String> allCommands = [
+  'help',
+  AnalyzeCommand.cmdName,
+  CreateCommand.cmdName,
+  CompileCommand.cmdName,
+  FixCommand.cmdName,
+  formatCmdName,
+  migrateCmdName,
+  PubCommand.cmdName,
+  RunCommand.cmdName,
+  'test'
+];
+
+/// The [String] identifier `dartdev`, used as the category in the events sent
+/// to analytics.
+const String _dartdev = 'dartdev';
+
+/// The [String] identifier `format`.
+const String formatCmdName = 'format';
+
+/// The [String] identifier `migrate`.
+const String migrateCmdName = 'migrate';
+
+/// The separator used to for joining the flag sets sent to analytics.
+const String _flagSeparator = ',';
+
+/// When some unknown command is used, for instance `dart foo`, the command is
+/// designated with this identifier.
+const String _unknownCommand = '<unknown>';
+
+/// The collection of custom dimensions understood by the analytics backend.
+/// When adding to this list, first ensure that the custom dimension is
+/// defined in the backend, or will be defined shortly after the relevant PR
+/// lands. The pattern here matches the flutter cli.
+enum CustomDimensions {
+  commandExitCode, // cd1
+  enabledExperiments, // cd2
+  commandFlags, // cd3
+}
+
+String cdKey(CustomDimensions cd) => 'cd${cd.index + 1}';
+
+Map<String, String> _useCdKeys(Map<CustomDimensions, String> parameters) {
+  return parameters.map(
+      (CustomDimensions k, String v) => MapEntry<String, String>(cdKey(k), v));
+}
+
+/// Utilities for parsing arguments passed to dartdev.  These utilities have all
+/// been marked as static to assist with testing, see events_test.dart.
+class ArgParserUtils {
+  /// Return the first member from [args] that occurs in [allCommands],
+  /// otherwise '<unknown>' is returned.
+  ///
+  /// 'help' is special cased to have 'dart analyze help', 'dart help analyze',
+  /// and 'dart analyze --help' all be recorded as a call to 'help' instead of
+  /// 'help' and 'analyze'.
+  static String getCommandStr(List<String> args) {
+    if (args.contains('help') ||
+        args.contains('-h') ||
+        args.contains('--help')) {
+      return 'help';
+    }
+    return args.firstWhere((arg) => allCommands.contains(arg),
+        orElse: () => _unknownCommand);
+  }
+
+  /// Return true if the first character of the passed [String] is '-'.
+  static bool isFlag(String arg) => arg != null && arg.startsWith('-');
+
+  /// Returns true if and only if the passed argument equals 'help', '--help' or
+  /// '-h'.
+  static bool isHelp(String arg) =>
+      arg == 'help' || arg == '--help' || arg == '-h';
+
+  /// Given some command in args, return the set of flags after the command.
+  static List<String> parseCommandFlags(String command, List<String> args) {
+    var result = <String>[];
+    if (args == null || args.isEmpty) {
+      return result;
+    }
+
+    var indexOfCmd = args.indexOf(command);
+    if (indexOfCmd < 0) {
+      return result;
+    }
+
+    for (var i = indexOfCmd + 1; i < args.length; i++) {
+      if (!isHelp(args[i]) && isFlag(args[i])) {
+        result.add(sanitizeFlag(args[i]));
+      }
+    }
+    return result;
+  }
+
+  /// Return the passed flag, only if it is considered a flag, see [isFlag], and
+  /// if '=' is in the flag, return only the contents of the left hand side of
+  /// the '='.
+  static String sanitizeFlag(String arg) {
+    if (isFlag(arg)) {
+      if (arg.contains('=')) {
+        return arg.substring(0, arg.indexOf('='));
+      } else {
+        return arg;
+      }
+    }
+    return '';
+  }
+}
+
+/// The [UsageEvent] for the format command.
+class FormatUsageEvent extends UsageEvent {
+  FormatUsageEvent(
+      {String label, @required int exitCode, @required List<String> args})
+      : super(formatCmdName, formatCmdName,
+            label: label, exitCode: exitCode, args: args);
+}
+
+/// The [UsageEvent] for the migrate command.
+class MigrateUsageEvent extends UsageEvent {
+  MigrateUsageEvent(
+      {String label, @required int exitCode, @required List<String> args})
+      : super(migrateCmdName, migrateCmdName,
+            label: label, exitCode: exitCode, args: args);
+}
+
+/// The superclass for all dartdev events, see the [send] method to see what is
+/// sent to analytics.
+abstract class UsageEvent {
+  /// The category stores the name of this cli tool, 'dartdev'. This matches the
+  /// pattern from the flutter cli tool which always passes 'flutter' as the
+  /// category.
+  final String category;
+
+  /// The action is the command, and optionally the subcommand, joined with '/',
+  /// an example here is 'pub/get'. The usagePath getter in each of the
+  final String action;
+
+  /// The command name being executed here, 'analyze' and 'pub' are examples.
+  final String command;
+
+  /// Labels are not used yet used when reporting dartdev analytics, but the API
+  /// is included here for possible future use.
+  final String label;
+
+  /// The [String] list of arguments passed to dartdev, the list of args is not
+  /// passed back via analytics itself, but is used to compute other values such
+  /// as the [enabledExperiments] which are passed back as part of analytics.
+  final List<String> args;
+
+  /// The exit code returned from this invocation of dartdev.
+  final int exitCode;
+
+  /// A comma separated list of enabled experiments passed into the dartdev
+  /// command. If the command doesn't use the experiments, they are not reported
+  /// in the [UsageEvent].
+  final String enabledExperiments;
+
+  /// A comma separated list of flags on this commands
+  final String commandFlags;
+
+  UsageEvent(
+    this.command,
+    this.action, {
+    this.label,
+    List<String> specifiedExperiments,
+    @required this.exitCode,
+    @required this.args,
+  })  : category = _dartdev,
+        enabledExperiments = specifiedExperiments?.join(_flagSeparator),
+        commandFlags = ArgParserUtils.parseCommandFlags(command, args)
+            .join(_flagSeparator);
+
+  Future send(Analytics analytics) {
+    final Map<String, String> parameters =
+        _useCdKeys(<CustomDimensions, String>{
+      if (exitCode != null)
+        CustomDimensions.commandExitCode: exitCode.toString(),
+      if (enabledExperiments != null)
+        CustomDimensions.enabledExperiments: enabledExperiments,
+      if (commandFlags != null) CustomDimensions.commandFlags: commandFlags,
+    });
+    return analytics.sendEvent(
+      category,
+      action,
+      label: label,
+      parameters: parameters,
+    );
+  }
+}
diff --git a/pkg/dartdev/pubspec.yaml b/pkg/dartdev/pubspec.yaml
index be43698..ecc93eb 100644
--- a/pkg/dartdev/pubspec.yaml
+++ b/pkg/dartdev/pubspec.yaml
@@ -15,6 +15,8 @@
   dart2native:
     path: ../dart2native
   dart_style: any
+  meta:
+    path: ../meta
   nnbd_migration:
     path: ../nnbd_migration
   path: ^1.0.0
diff --git a/pkg/dartdev/test/analytics_test.dart b/pkg/dartdev/test/analytics_test.dart
index 3ce90de..31355df7 100644
--- a/pkg/dartdev/test/analytics_test.dart
+++ b/pkg/dartdev/test/analytics_test.dart
@@ -7,7 +7,6 @@
 
 void main() {
   group('DisabledAnalytics', disabledAnalyticsObject);
-  group('utils', utils);
 }
 
 void disabledAnalyticsObject() {
@@ -19,21 +18,3 @@
     expect(diabledAnalytics.firstRun, isFalse);
   });
 }
-
-void utils() {
-  test('getCommandStr', () {
-    var commands = <String>['help', 'foo', 'bar', 'baz'];
-
-    // base cases
-    expect(getCommandStr(['help'], commands), 'help');
-    expect(getCommandStr(['bar', 'help'], commands), 'help');
-    expect(getCommandStr(['help', 'bar'], commands), 'help');
-    expect(getCommandStr(['bar', '-h'], commands), 'help');
-    expect(getCommandStr(['bar', '--help'], commands), 'help');
-
-    // non-trivial tests
-    expect(getCommandStr(['foo'], commands), 'foo');
-    expect(getCommandStr(['bar', 'baz'], commands), 'bar');
-    expect(getCommandStr(['bazz'], commands), '<unknown>');
-  });
-}
diff --git a/pkg/dartdev/test/core_test.dart b/pkg/dartdev/test/core_test.dart
index bc10cb9..0944f88 100644
--- a/pkg/dartdev/test/core_test.dart
+++ b/pkg/dartdev/test/core_test.dart
@@ -5,6 +5,13 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:dartdev/src/commands/analyze.dart';
+import 'package:dartdev/src/commands/compile.dart';
+import 'package:dartdev/src/commands/create.dart';
+import 'package:dartdev/src/commands/fix.dart';
+import 'package:dartdev/src/commands/pub.dart';
+import 'package:dartdev/src/commands/run.dart';
+import 'package:dartdev/src/commands/test.dart';
 import 'package:dartdev/src/core.dart';
 import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
@@ -12,10 +19,82 @@
 import 'utils.dart';
 
 void main() {
+  group('DartdevCommand', _dartdevCommand);
   group('PackageConfig', _packageConfig);
   group('Project', _project);
 }
 
+void _dartdevCommand() {
+  void _assertDartdevCommandProperties(
+      DartdevCommand command, String name, String usagePath,
+      [int subcommandCount = 0]) {
+    expect(command, isNotNull);
+    expect(command.name, name);
+    expect(command.description, isNotEmpty);
+    expect(command.project, isNotNull);
+    expect(command.argParser, isNotNull);
+    expect(command.usagePath, usagePath);
+    expect(command.subcommands.length, subcommandCount);
+  }
+
+  test('analyze', () {
+    _assertDartdevCommandProperties(AnalyzeCommand(), 'analyze', 'analyze');
+  });
+
+  test('compile', () {
+    _assertDartdevCommandProperties(CompileCommand(), 'compile', 'compile', 5);
+  });
+
+  test('compile/js', () {
+    _assertDartdevCommandProperties(
+        CompileCommand().subcommands['js'], 'js', 'compile/js');
+  });
+
+  test('compile/jit-snapshot', () {
+    _assertDartdevCommandProperties(
+        CompileCommand().subcommands['jit-snapshot'],
+        'jit-snapshot',
+        'compile/jit-snapshot');
+  });
+
+  test('compile/kernel', () {
+    _assertDartdevCommandProperties(
+        CompileCommand().subcommands['kernel'], 'kernel', 'compile/kernel');
+  });
+
+  test('compile/exe', () {
+    _assertDartdevCommandProperties(
+        CompileCommand().subcommands['exe'], 'exe', 'compile/exe');
+  });
+
+  test('compile/aot-snapshot', () {
+    _assertDartdevCommandProperties(
+        CompileCommand().subcommands['aot-snapshot'],
+        'aot-snapshot',
+        'compile/aot-snapshot');
+  });
+
+  test('create', () {
+    _assertDartdevCommandProperties(CreateCommand(), 'create', 'create');
+  });
+
+  test('fix', () {
+    _assertDartdevCommandProperties(FixCommand(), 'fix', 'fix');
+  });
+
+  test('pub', () {
+    _assertDartdevCommandProperties(PubCommand(), 'pub', 'pub');
+  });
+
+  test('run', () {
+    _assertDartdevCommandProperties(RunCommand(), 'run', 'run');
+  });
+
+  test('test', () {
+    _assertDartdevCommandProperties(TestCommand(), 'test', 'test');
+  });
+}
+
 void _packageConfig() {
   test('packages', () {
     PackageConfig packageConfig = PackageConfig(jsonDecode(_packageData));
diff --git a/pkg/dartdev/test/events_test.dart b/pkg/dartdev/test/events_test.dart
new file mode 100644
index 0000000..c39e7c4
--- /dev/null
+++ b/pkg/dartdev/test/events_test.dart
@@ -0,0 +1,130 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:dartdev/dartdev.dart';
+import 'package:dartdev/src/events.dart';
+import 'package:test/test.dart';
+
+void main() {
+  group('ArgParserUtils', _argParserUtils);
+  group('event constant', _constants);
+}
+
+void _argParserUtils() {
+  test('getCommandStr help', () {
+    expect(ArgParserUtils.getCommandStr(['help']), 'help');
+    expect(ArgParserUtils.getCommandStr(['analyze', 'help']), 'help');
+    expect(ArgParserUtils.getCommandStr(['help', 'analyze']), 'help');
+    expect(ArgParserUtils.getCommandStr(['analyze', '-h']), 'help');
+    expect(ArgParserUtils.getCommandStr(['analyze', '--help']), 'help');
+  });
+
+  test('getCommandStr command', () {
+    expect(ArgParserUtils.getCommandStr(['analyze']), 'analyze');
+    expect(ArgParserUtils.getCommandStr(['analyze', 'foo']), 'analyze');
+    expect(ArgParserUtils.getCommandStr(['foo', 'bar']), '<unknown>');
+    expect(ArgParserUtils.getCommandStr([]), '<unknown>');
+    expect(ArgParserUtils.getCommandStr(['']), '<unknown>');
+  });
+
+  test('isHelp false', () {
+    expect(ArgParserUtils.isHelp(null), isFalse);
+    expect(ArgParserUtils.isHelp(''), isFalse);
+    expect(ArgParserUtils.isHelp(' '), isFalse);
+    expect(ArgParserUtils.isHelp('-help'), isFalse);
+    expect(ArgParserUtils.isHelp('--HELP'), isFalse);
+    expect(ArgParserUtils.isHelp('--Help'), isFalse);
+    expect(ArgParserUtils.isHelp('Help'), isFalse);
+    expect(ArgParserUtils.isHelp('HELP'), isFalse);
+    expect(ArgParserUtils.isHelp('foo'), isFalse);
+  });
+
+  test('isHelp true', () {
+    expect(ArgParserUtils.isHelp('help'), isTrue);
+    expect(ArgParserUtils.isHelp('--help'), isTrue);
+    expect(ArgParserUtils.isHelp('-h'), isTrue);
+  });
+
+  test('isFlag false', () {
+    expect(ArgParserUtils.isFlag(null), isFalse);
+    expect(ArgParserUtils.isFlag(''), isFalse);
+    expect(ArgParserUtils.isFlag(' '), isFalse);
+    expect(ArgParserUtils.isFlag('help'), isFalse);
+    expect(ArgParserUtils.isFlag('_flag'), isFalse);
+  });
+
+  test('isFlag true', () {
+    expect(ArgParserUtils.isFlag('-'), isTrue);
+    expect(ArgParserUtils.isFlag('--'), isTrue);
+    expect(ArgParserUtils.isFlag('--flag'), isTrue);
+    expect(ArgParserUtils.isFlag('--help'), isTrue);
+    expect(ArgParserUtils.isFlag('-h'), isTrue);
+  });
+
+  test('parseCommandFlags analyze', () {
+    expect(
+        ArgParserUtils.parseCommandFlags('analyze', [
+          '-g',
+          'analyze',
+        ]),
+        <String>[]);
+    expect(
+        ArgParserUtils.parseCommandFlags('analyze', [
+          '-g',
+          'analyze',
+          '--one',
+          '--two',
+          '--three=bar',
+          '-f',
+          '--fatal-infos',
+          '-h',
+          'five'
+        ]),
+        <String>['--one', '--two', '--three', '-f', '--fatal-infos']);
+  });
+
+  test('parseCommandFlags trivial', () {
+    expect(ArgParserUtils.parseCommandFlags('foo', []), <String>[]);
+    expect(ArgParserUtils.parseCommandFlags('foo', ['']), <String>[]);
+    expect(
+        ArgParserUtils.parseCommandFlags('foo', ['bar', '-flag']), <String>[]);
+    expect(
+        ArgParserUtils.parseCommandFlags('foo', ['--global', 'bar', '-flag']),
+        <String>[]);
+    expect(ArgParserUtils.parseCommandFlags('foo', ['--global', 'fo', '-flag']),
+        <String>[]);
+    expect(
+        ArgParserUtils.parseCommandFlags('foo', ['--global', 'FOO', '-flag']),
+        <String>[]);
+  });
+
+  test('parseCommandFlags exclude help', () {
+    expect(
+        ArgParserUtils.parseCommandFlags(
+            'analyze', ['-g', 'analyze', '--flag', '--help']),
+        <String>['--flag']);
+    expect(
+        ArgParserUtils.parseCommandFlags(
+            'analyze', ['-g', 'analyze', '--flag', '-h']),
+        <String>['--flag']);
+    expect(
+        ArgParserUtils.parseCommandFlags(
+            'analyze', ['-g', 'analyze', '--flag', 'help']),
+        <String>['--flag']);
+  });
+
+  test('sanitizeFlag', () {
+    expect(ArgParserUtils.sanitizeFlag(null), '');
+    expect(ArgParserUtils.sanitizeFlag(''), '');
+    expect(ArgParserUtils.sanitizeFlag('foo'), '');
+    expect(ArgParserUtils.sanitizeFlag('--foo'), '--foo');
+    expect(ArgParserUtils.sanitizeFlag('--foo=bar'), '--foo');
+  });
+}
+
+void _constants() {
+  test('allCommands', () {
+    expect(allCommands, DartdevRunner([]).commands.keys.toList());
+  });
+}
diff --git a/pkg/dartdev/test/test_all.dart b/pkg/dartdev/test/test_all.dart
index a3b6db4..ca092a4 100644
--- a/pkg/dartdev/test/test_all.dart
+++ b/pkg/dartdev/test/test_all.dart
@@ -17,6 +17,7 @@
 import 'commands/run_test.dart' as run;
 import 'commands/test_test.dart' as test;
 import 'core_test.dart' as core;
+import 'events_test.dart' as events;
 import 'experiments_test.dart' as experiments;
 import 'sdk_test.dart' as sdk;
 import 'utils_test.dart' as utils;
@@ -26,6 +27,7 @@
     analytics.main();
     analyze.main();
     create.main();
+    events.main();
     experiments.main();
     fix.main();
     flag.main();
diff --git a/pkg/dds/lib/dds.dart b/pkg/dds/lib/dds.dart
index aeb39a9..f35e4d3 100644
--- a/pkg/dds/lib/dds.dart
+++ b/pkg/dds/lib/dds.dart
@@ -114,7 +114,7 @@
   /// Stop accepting requests after gracefully handling existing requests.
   Future<void> shutdown();
 
-  /// Set to `true` if this isntance of [DartDevelopmentService] requires an
+  /// Set to `true` if this instance of [DartDevelopmentService] requires an
   /// authentication code to connect.
   bool get authCodesEnabled;
 
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 96575f1..284c144 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -2558,7 +2558,9 @@
           typeArgument.nullability == Nullability.legacy;
       var nullability = nullable
           ? Nullability.nullable
-          : legacy ? Nullability.legacy : Nullability.nonNullable;
+          : legacy
+              ? Nullability.legacy
+              : Nullability.nonNullable;
       return _emitInterfaceType(
           typeArgument.withDeclaredNullability(nullability));
     } else if (typeArgument is NeverType) {
diff --git a/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart b/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart
index 7baad48..81776ac 100644
--- a/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart
+++ b/pkg/dev_compiler/lib/src/kernel/kernel_helpers.dart
@@ -191,7 +191,9 @@
 Expression getInvocationReceiver(InvocationExpression node) =>
     node is MethodInvocation
         ? node.receiver
-        : node is DirectMethodInvocation ? node.receiver : null;
+        : node is DirectMethodInvocation
+            ? node.receiver
+            : null;
 
 bool isInlineJS(Member e) =>
     e is Procedure &&
diff --git a/pkg/dev_compiler/lib/src/kernel/type_table.dart b/pkg/dev_compiler/lib/src/kernel/type_table.dart
index 2d6b5cf..8421a66 100644
--- a/pkg/dev_compiler/lib/src/kernel/type_table.dart
+++ b/pkg/dev_compiler/lib/src/kernel/type_table.dart
@@ -74,7 +74,9 @@
   String _typeString(DartType type, {bool flat = false}) {
     var nullability = type.declaredNullability == Nullability.legacy
         ? 'L'
-        : type.declaredNullability == Nullability.nullable ? 'N' : '';
+        : type.declaredNullability == Nullability.nullable
+            ? 'N'
+            : '';
     assert(isKnownDartTypeImplementor(type));
     if (type is InterfaceType) {
       var name = '${type.classNode.name}$nullability';
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart
index 18fa6ce..25474d0 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart
@@ -19,21 +19,23 @@
 bool get verbose => false;
 
 class ModuleConfiguration {
-  final String outputPath;
-  final String moduleName;
+  final Uri root;
+  final String outputDir;
   final String libraryUri;
+  final String moduleName;
   final String jsFileName;
   final String fullDillFileName;
 
   ModuleConfiguration(
-      {this.outputPath,
+      {this.root,
+      this.outputDir,
       this.moduleName,
       this.libraryUri,
       this.jsFileName,
       this.fullDillFileName});
 
-  String get jsPath => p.join(outputPath, jsFileName);
-  String get fullDillPath => p.join(outputPath, fullDillFileName);
+  Uri get jsPath => root.resolve('$outputDir/$jsFileName');
+  Uri get fullDillPath => root.resolve('$outputDir/$fullDillFileName');
 }
 
 class TestProjectConfiguration {
@@ -43,36 +45,40 @@
   TestProjectConfiguration(this.rootDirectory);
 
   ModuleConfiguration get mainModule => ModuleConfiguration(
-      outputPath: outputPath,
+      root: root,
+      outputDir: outputDir,
       moduleName: 'packages/_testPackage/main',
       libraryUri: 'org-dartlang-app:/lib/main.dart',
       jsFileName: 'main.js',
       fullDillFileName: 'main.full.dill');
 
   ModuleConfiguration get testModule => ModuleConfiguration(
-      outputPath: outputPath,
+      root: root,
+      outputDir: outputDir,
       moduleName: 'packages/_testPackage/test_library',
       libraryUri: 'package:_testPackage/test_library.dart',
       jsFileName: 'test_library.js',
       fullDillFileName: 'test_library.full.dill');
 
   ModuleConfiguration get testModule2 => ModuleConfiguration(
-      outputPath: outputPath,
+      root: root,
+      outputDir: outputDir,
       moduleName: 'packages/_testPackage/test_library2',
       libraryUri: 'package:_testPackage/test_library2.dart',
       jsFileName: 'test_library2.js',
       fullDillFileName: 'test_library2.full.dill');
 
-  String get root => rootDirectory.path;
-  String get outputPath => p.join(root, outputDir);
-  String get packagesPath => p.join(root, '.packages');
+  String get rootPath => rootDirectory.path;
+  Uri get root => rootDirectory.uri;
+  Uri get outputPath => root.resolve(outputDir);
+  Uri get packagesPath => root.resolve('.packages');
 
-  String get sdkRoot => computePlatformBinariesLocation().path;
-  String get sdkSummaryPath => p.join(sdkRoot, 'ddc_sdk.dill');
-  String get librariesPath => p.join(sdkRoot, 'lib', 'libraries.json');
+  Uri get sdkRoot => computePlatformBinariesLocation();
+  Uri get sdkSummaryPath => sdkRoot.resolve('ddc_sdk.dill');
+  Uri get librariesPath => sdkRoot.resolve('lib/libraries.json');
 
   void createTestProject() {
-    var pubspec = rootDirectory.uri.resolve('pubspec.yaml');
+    var pubspec = root.resolve('pubspec.yaml');
     File.fromUri(pubspec)
       ..createSync()
       ..writeAsStringSync('''
@@ -83,14 +89,14 @@
   sdk: '>=2.8.0 <3.0.0'
 ''');
 
-    var packages = rootDirectory.uri.resolve('.packages');
+    var packages = root.resolve('.packages');
     File.fromUri(packages)
       ..createSync()
       ..writeAsStringSync('''
 _testPackage:lib/
 ''');
 
-    var main = rootDirectory.uri.resolve('lib/main.dart');
+    var main = root.resolve('lib/main.dart');
     File.fromUri(main)
       ..createSync(recursive: true)
       ..writeAsStringSync('''
@@ -108,7 +114,7 @@
 }
 ''');
 
-    var testLibrary = rootDirectory.uri.resolve('lib/test_library.dart');
+    var testLibrary = root.resolve('lib/test_library.dart');
     File.fromUri(testLibrary)
       ..createSync()
       ..writeAsStringSync('''
@@ -127,7 +133,7 @@
 }
 ''');
 
-    var testLibrary2 = rootDirectory.uri.resolve('lib/test_library2.dart');
+    var testLibrary2 = root.resolve('lib/test_library2.dart');
     File.fromUri(testLibrary2)
       ..createSync()
       ..writeAsStringSync('''
@@ -163,15 +169,15 @@
 
       inputs = [
         {
-          'path': config.mainModule.fullDillPath,
+          'path': config.mainModule.fullDillPath.path,
           'moduleName': config.mainModule.moduleName
         },
         {
-          'path': config.testModule.fullDillPath,
+          'path': config.testModule.fullDillPath.path,
           'moduleName': config.testModule.moduleName
         },
         {
-          'path': config.testModule2.fullDillPath,
+          'path': config.testModule2.fullDillPath.path,
           'moduleName': config.testModule2.moduleName
         },
       ];
@@ -188,12 +194,13 @@
       requestController = StreamController<Map<String, dynamic>>();
       responseController = StreamController<Map<String, dynamic>>();
       worker = await ExpressionCompilerWorker.create(
-        librariesSpecificationUri: Uri.file(config.librariesPath),
+        librariesSpecificationUri: config.librariesPath,
         // We should be able to load everything from dill and not require
-        // source parsing. Webdev and google3 integration currently rely on that.
-        // Make the test fail on source reading by not providing a packages.
+        // source parsing. Webdev and google3 integration currently rely on
+        // that. Make the test fail on source reading by not providing a
+        // packages file.
         packagesFile: null,
-        sdkSummary: Uri.file(config.sdkSummaryPath),
+        sdkSummary: config.sdkSummaryPath,
         fileSystem: fileSystem,
         requestStream: requestController.stream,
         sendResponse: responseController.add,
@@ -360,7 +367,7 @@
 
       inputs = [
         {
-          'path': config.mainModule.fullDillPath,
+          'path': config.mainModule.fullDillPath.path,
           'moduleName': config.mainModule.moduleName
         },
       ];
@@ -377,9 +384,9 @@
       requestController = StreamController<Map<String, dynamic>>();
       responseController = StreamController<Map<String, dynamic>>();
       worker = await ExpressionCompilerWorker.create(
-        librariesSpecificationUri: Uri.file(config.librariesPath),
+        librariesSpecificationUri: config.librariesPath,
         packagesFile: null,
-        sdkSummary: Uri.file(config.sdkSummaryPath),
+        sdkSummary: config.sdkSummaryPath,
         fileSystem: fileSystem,
         requestStream: requestController.stream,
         sendResponse: responseController.add,
@@ -505,7 +512,7 @@
     var dartdevc =
         p.join(p.dirname(dart), 'snapshots', 'dartdevc.dart.snapshot');
 
-    Directory(config.outputPath)..createSync();
+    Directory.fromUri(config.outputPath)..createSync();
 
     // generate test_library2.full.dill
     var args = [
@@ -513,21 +520,21 @@
       config.testModule2.libraryUri,
       '--no-summarize',
       '-o',
-      config.testModule2.jsPath,
+      config.testModule2.jsPath.toFilePath(),
       '--source-map',
       '--experimental-emit-debug-metadata',
       '--experimental-output-compiled-kernel',
       '--dart-sdk-summary',
-      config.sdkSummaryPath,
+      config.sdkSummaryPath.path,
       '--multi-root',
-      config.root,
+      '${config.root}',
       '--multi-root-scheme',
       'org-dartlang-app',
       '--packages',
-      config.packagesPath,
+      config.packagesPath.path,
     ];
 
-    var exitCode = await runProcess(dart, args, config.root);
+    var exitCode = await runProcess(dart, args, config.rootPath);
     if (exitCode != 0) {
       return exitCode;
     }
@@ -540,21 +547,21 @@
       '--summary',
       '${config.testModule2.fullDillPath}=${config.testModule2.moduleName}',
       '-o',
-      config.testModule.jsPath,
+      config.testModule.jsPath.toFilePath(),
       '--source-map',
       '--experimental-emit-debug-metadata',
       '--experimental-output-compiled-kernel',
       '--dart-sdk-summary',
-      config.sdkSummaryPath,
+      config.sdkSummaryPath.path,
       '--multi-root',
-      config.root,
+      '${config.root}',
       '--multi-root-scheme',
       'org-dartlang-app',
       '--packages',
-      config.packagesPath,
+      config.packagesPath.path,
     ];
 
-    exitCode = await runProcess(dart, args, config.root);
+    exitCode = await runProcess(dart, args, config.rootPath);
     if (exitCode != 0) {
       return exitCode;
     }
@@ -569,21 +576,21 @@
       '--summary',
       '${config.testModule.fullDillPath}=${config.testModule.moduleName}',
       '-o',
-      config.mainModule.jsPath,
+      config.mainModule.jsPath.toFilePath(),
       '--source-map',
       '--experimental-emit-debug-metadata',
       '--experimental-output-compiled-kernel',
       '--dart-sdk-summary',
-      config.sdkSummaryPath,
+      config.sdkSummaryPath.path,
       '--multi-root',
-      config.root,
+      '${config.root}',
       '--multi-root-scheme',
       'org-dartlang-app',
       '--packages',
-      config.packagesPath,
+      config.packagesPath.path,
     ];
 
-    return await runProcess(dart, args, config.root);
+    return await runProcess(dart, args, config.rootPath);
   }
 }
 
@@ -605,9 +612,9 @@
       '--target',
       'ddc',
       '--output',
-      config.mainModule.fullDillPath,
+      config.mainModule.fullDillPath.path,
       '--dart-sdk-summary',
-      config.sdkSummaryPath,
+      config.sdkSummaryPath.path,
       '--exclude-non-sources',
       '--source',
       config.mainModule.libraryUri,
@@ -616,7 +623,7 @@
       '--source',
       config.testModule2.libraryUri,
       '--multi-root',
-      config.root,
+      '${config.root}',
       '--multi-root-scheme',
       'org-dartlang-app',
       '--packages-file',
@@ -624,7 +631,7 @@
       '--verbose'
     ];
 
-    return await runProcess(dart, args, config.root);
+    return await runProcess(dart, args, config.rootPath);
   }
 }
 
diff --git a/pkg/vm_service/CHANGELOG.md b/pkg/vm_service/CHANGELOG.md
index 466d85f..049e01d1 100644
--- a/pkg/vm_service/CHANGELOG.md
+++ b/pkg/vm_service/CHANGELOG.md
@@ -2,7 +2,7 @@
 
 ## 5.0.0
 
-- **breaking**: Update to version `4.0.0` of the spec.
+- **breaking**: Update to version `3.39.0` of the spec.
   - Removes `ClientName` and `WebSocketTarget` objects
   - Removes `getClientName`, `getWebSocketTarget`, `requirePermissionToResume`,
     and `setClientName` RPCs.
diff --git a/pkg/vm_service/example/sample_isolates.dart b/pkg/vm_service/example/sample_isolates.dart
index 3dd216b..e57fd09 100644
--- a/pkg/vm_service/example/sample_isolates.dart
+++ b/pkg/vm_service/example/sample_isolates.dart
@@ -2,7 +2,6 @@
 // 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 'dart:isolate';
 
 main(List<String> args) async {
diff --git a/pkg/vm_service/java/src/org/dartlang/vm/service/VmServiceBase.java b/pkg/vm_service/java/src/org/dartlang/vm/service/VmServiceBase.java
index c44b31d..207cdc5 100644
--- a/pkg/vm_service/java/src/org/dartlang/vm/service/VmServiceBase.java
+++ b/pkg/vm_service/java/src/org/dartlang/vm/service/VmServiceBase.java
@@ -132,21 +132,6 @@
 
       @Override
       public void received(Version version) {
-        int major = version.getMajor();
-        int minor = version.getMinor();
-        if (major != VmService.versionMajor || minor != VmService.versionMinor) {
-          if (major == 2 || major == 3) {
-            Logging.getLogger().logInformation(
-                "Difference in protocol version: client=" + VmService.versionMajor + "."
-                    + VmService.versionMinor + " vm=" + major + "." + minor);
-          } else {
-            String msg = "Incompatible protocol version: client=" + VmService.versionMajor + "."
-                + VmService.versionMinor + " vm=" + major + "." + minor;
-            Logging.getLogger().logError(msg);
-            errMsg[0] = msg;
-          }
-        }
-
         vmService.runtimeVersion = version;
 
         latch.countDown();
diff --git a/pkg/vm_service/java/version.properties b/pkg/vm_service/java/version.properties
index db1ef8e5..e076127 100644
--- a/pkg/vm_service/java/version.properties
+++ b/pkg/vm_service/java/version.properties
@@ -1 +1 @@
-version=4.0
+version=3.39
diff --git a/pkg/vm_service/lib/src/dart_io_extensions.dart b/pkg/vm_service/lib/src/dart_io_extensions.dart
index ef89c64..4297e45 100644
--- a/pkg/vm_service/lib/src/dart_io_extensions.dart
+++ b/pkg/vm_service/lib/src/dart_io_extensions.dart
@@ -4,8 +4,6 @@
 
 // TODO(bkonyi): autogenerate from service_extensions.md
 
-import 'dart:async';
-
 import 'package:meta/meta.dart';
 
 import 'vm_service.dart';
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index c518b25..4f63e01 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -28,7 +28,7 @@
         HeapSnapshotObjectNoData,
         HeapSnapshotObjectNullData;
 
-const String vmServiceVersion = '4.0.0';
+const String vmServiceVersion = '3.39.0';
 
 /// @optional
 const String optional = 'optional';
diff --git a/pkg/vm_service/pubspec.yaml b/pkg/vm_service/pubspec.yaml
index fff9595..66b2910 100644
--- a/pkg/vm_service/pubspec.yaml
+++ b/pkg/vm_service/pubspec.yaml
@@ -2,7 +2,7 @@
 description: >-
   A library to communicate with a service implementing the Dart VM
   service protocol.
-version: 5.0.0
+version: 5.0.0+1
 
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/vm_service
 
diff --git a/pkg/vm_service/test/async_generator_breakpoint_test.dart b/pkg/vm_service/test/async_generator_breakpoint_test.dart
index 3cf2655..e709594 100644
--- a/pkg/vm_service/test/async_generator_breakpoint_test.dart
+++ b/pkg/vm_service/test/async_generator_breakpoint_test.dart
@@ -2,7 +2,6 @@
 // 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/test_helper.dart';
diff --git a/pkg/vm_service/test/async_scope_test.dart b/pkg/vm_service/test/async_scope_test.dart
index 80813b6..9573e69 100644
--- a/pkg/vm_service/test/async_scope_test.dart
+++ b/pkg/vm_service/test/async_scope_test.dart
@@ -2,15 +2,14 @@
 // 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 'dart:developer';
 import 'package:vm_service/vm_service.dart';
 import 'package:test/test.dart';
 import 'common/service_test_common.dart';
 import 'common/test_helper.dart';
 
-const int LINE_A = 20;
-const int LINE_B = 26;
+const int LINE_A = 19;
+const int LINE_B = 25;
 
 foo() {}
 
diff --git a/pkg/vm_service/test/evaluate_with_scope_test.dart b/pkg/vm_service/test/evaluate_with_scope_test.dart
index 38588d8..8dfd4d4 100644
--- a/pkg/vm_service/test/evaluate_with_scope_test.dart
+++ b/pkg/vm_service/test/evaluate_with_scope_test.dart
@@ -2,8 +2,6 @@
 // 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:vm_service/vm_service.dart';
 import 'package:test/test.dart';
 import 'common/test_helper.dart';
diff --git a/pkg/vm_service/test/get_allocation_profile_rpc_test.dart b/pkg/vm_service/test/get_allocation_profile_rpc_test.dart
index 0a35d093..c4540e5 100644
--- a/pkg/vm_service/test/get_allocation_profile_rpc_test.dart
+++ b/pkg/vm_service/test/get_allocation_profile_rpc_test.dart
@@ -2,7 +2,6 @@
 // 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:vm_service/vm_service.dart';
 import 'package:test/test.dart';
 
diff --git a/pkg/vm_service/test/get_cpu_samples_rpc_test.dart b/pkg/vm_service/test/get_cpu_samples_rpc_test.dart
index de49353..8b9b143 100644
--- a/pkg/vm_service/test/get_cpu_samples_rpc_test.dart
+++ b/pkg/vm_service/test/get_cpu_samples_rpc_test.dart
@@ -2,8 +2,6 @@
 // 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:vm_service/vm_service.dart';
 import 'package:test/test.dart';
 
diff --git a/pkg/vm_service/test/http_enable_timeline_logging_service_test.dart b/pkg/vm_service/test/http_enable_timeline_logging_service_test.dart
index 418b823..0b849c6 100644
--- a/pkg/vm_service/test/http_enable_timeline_logging_service_test.dart
+++ b/pkg/vm_service/test/http_enable_timeline_logging_service_test.dart
@@ -2,7 +2,6 @@
 // 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:vm_service/vm_service.dart';
 import 'package:vm_service/src/dart_io_extensions.dart';
 import 'package:test/test.dart';
diff --git a/runtime/observatory/tests/service/get_version_rpc_test.dart b/runtime/observatory/tests/service/get_version_rpc_test.dart
index 7ee46a0..8e949ec 100644
--- a/runtime/observatory/tests/service/get_version_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_version_rpc_test.dart
@@ -11,8 +11,8 @@
   (VM vm) async {
     var result = await vm.invokeRpcNoUpgrade('getVersion', {});
     expect(result['type'], equals('Version'));
-    expect(result['major'], equals(4));
-    expect(result['minor'], equals(0));
+    expect(result['major'], equals(3));
+    expect(result['minor'], equals(39));
     expect(result['_privateMajor'], equals(0));
     expect(result['_privateMinor'], equals(0));
   },
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc
index f33a41c..9c7eabc 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc
@@ -2290,13 +2290,6 @@
   return true;
 }
 
-bool FlowGraphCompiler::ShouldUseTypeTestingStubFor(bool optimizing,
-                                                    const AbstractType& type) {
-  return FLAG_precompiled_mode ||
-         (optimizing &&
-          (type.IsTypeParameter() || (type.IsType() && type.IsInstantiated())));
-}
-
 FlowGraphCompiler::TypeTestStubKind
 FlowGraphCompiler::GetTypeTestStubKindForTypeParameter(
     const TypeParameter& type_param) {
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.h b/runtime/vm/compiler/backend/flow_graph_compiler.h
index 47e9530..9b1764e 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.h
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.h
@@ -568,11 +568,6 @@
                                 const String& dst_name,
                                 LocationSummary* locs);
 
-  // Returns true if we can use a type testing stub based assert
-  // assignable code pattern for the given type.
-  static bool ShouldUseTypeTestingStubFor(bool optimizing,
-                                          const AbstractType& type);
-
   void GenerateAssertAssignableViaTypeTestingStub(CompileType* receiver_type,
                                                   TokenPosition token_pos,
                                                   intptr_t deopt_id,
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc b/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
index bfc9b81..465352b 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
@@ -703,67 +703,19 @@
   ASSERT(!token_pos.IsClassifying());
   ASSERT(CheckAssertAssignableTypeTestingABILocations(*locs));
 
-  compiler::Label is_assignable_fast, is_assignable, runtime_call;
-
-  // Generate inline type check, linking to runtime call if not assignable.
-  SubtypeTestCache& test_cache = SubtypeTestCache::ZoneHandle(zone());
-  static_assert(
-      TypeTestABI::kFunctionTypeArgumentsReg <
-          TypeTestABI::kInstantiatorTypeArgumentsReg,
-      "Should be ordered to push and load arguments with one instruction");
-  static RegList type_args = (1 << TypeTestABI::kFunctionTypeArgumentsReg) |
-                             (1 << TypeTestABI::kInstantiatorTypeArgumentsReg);
-
   if (locs->in(1).IsConstant()) {
     const auto& dst_type = AbstractType::Cast(locs->in(1).constant());
     ASSERT(dst_type.IsFinalized());
 
     if (dst_type.IsTopTypeForSubtyping()) return;  // No code needed.
 
-    if (ShouldUseTypeTestingStubFor(is_optimizing(), dst_type)) {
-      GenerateAssertAssignableViaTypeTestingStub(receiver_type, token_pos,
-                                                 deopt_id, dst_name, locs);
-      return;
-    }
-
-    if (Instance::NullIsAssignableTo(dst_type)) {
-      __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
-      __ b(&is_assignable_fast, EQ);
-    }
-
-    __ PushList(type_args);
-
-    test_cache = GenerateInlineInstanceof(token_pos, dst_type, &is_assignable,
-                                          &runtime_call);
+    GenerateAssertAssignableViaTypeTestingStub(receiver_type, token_pos,
+                                               deopt_id, dst_name, locs);
+    return;
   } else {
     // TODO(dartbug.com/40813): Handle setting up the non-constant case.
     UNREACHABLE();
   }
-
-  __ Bind(&runtime_call);
-  __ ldm(IA, SP, type_args);
-  __ PushObject(Object::null_object());  // Make room for the result.
-  __ Push(TypeTestABI::kInstanceReg);    // Push the source object.
-  // Push the type of the destination.
-  if (locs->in(1).IsConstant()) {
-    __ PushObject(locs->in(1).constant());
-  } else {
-    // TODO(dartbug.com/40813): Handle setting up the non-constant case.
-    UNREACHABLE();
-  }
-  __ PushList(type_args);
-  __ PushObject(dst_name);  // Push the name of the destination.
-  __ LoadUniqueObject(R0, test_cache);
-  __ Push(R0);
-  __ PushImmediate(Smi::RawValue(kTypeCheckFromInline));
-  GenerateRuntimeCall(token_pos, deopt_id, kTypeCheckRuntimeEntry, 7, locs);
-  // Pop the parameters supplied to the runtime entry. The result of the
-  // type check runtime call is the checked value.
-  __ Drop(7);
-  __ Pop(TypeTestABI::kInstanceReg);
-  __ Bind(&is_assignable);
-  __ PopList(type_args);
-  __ Bind(&is_assignable_fast);
 }
 
 void FlowGraphCompiler::GenerateAssertAssignableViaTypeTestingStub(
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc b/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
index b235c33..bc6983e 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
@@ -669,67 +669,19 @@
   ASSERT(!TokenPosition(token_pos).IsClassifying());
   ASSERT(CheckAssertAssignableTypeTestingABILocations(*locs));
 
-  compiler::Label is_assignable_fast, is_assignable, runtime_call;
-  // Generate inline type check, linking to runtime call if not assignable.
-  SubtypeTestCache& test_cache = SubtypeTestCache::ZoneHandle(zone());
-
   if (locs->in(1).IsConstant()) {
     const auto& dst_type = AbstractType::Cast(locs->in(1).constant());
     ASSERT(dst_type.IsFinalized());
 
     if (dst_type.IsTopTypeForSubtyping()) return;  // No code needed.
 
-    if (ShouldUseTypeTestingStubFor(is_optimizing(), dst_type)) {
-      GenerateAssertAssignableViaTypeTestingStub(receiver_type, token_pos,
-                                                 deopt_id, dst_name, locs);
-      return;
-    }
-
-    if (Instance::NullIsAssignableTo(dst_type)) {
-      __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
-      __ b(&is_assignable_fast, EQ);
-    }
-
-    __ PushPair(TypeTestABI::kFunctionTypeArgumentsReg,
-                TypeTestABI::kInstantiatorTypeArgumentsReg);
-
-    test_cache = GenerateInlineInstanceof(token_pos, dst_type, &is_assignable,
-                                          &runtime_call);
+    GenerateAssertAssignableViaTypeTestingStub(receiver_type, token_pos,
+                                               deopt_id, dst_name, locs);
+    return;
   } else {
     // TODO(dartbug.com/40813): Handle setting up the non-constant case.
     UNREACHABLE();
   }
-
-  __ Bind(&runtime_call);
-  __ ldp(TypeTestABI::kFunctionTypeArgumentsReg,
-         TypeTestABI::kInstantiatorTypeArgumentsReg,
-         compiler::Address(SP, 0 * kWordSize, compiler::Address::PairOffset));
-  // Make room for the result and push the source object.
-  __ PushPair(TypeTestABI::kInstanceReg, NULL_REG);
-  // Push the destination type and the instantiator type arguments.
-  if (locs->in(1).IsConstant()) {
-    __ LoadObject(TMP, locs->in(1).constant());
-    __ PushPair(TypeTestABI::kInstantiatorTypeArgumentsReg, TMP);
-  } else {
-    // TODO(dartbug.com/40813): Handle setting up the non-constant case.
-    UNREACHABLE();
-  }
-  // Push the function type arguments and the name of the destination.
-  __ LoadObject(TMP, dst_name);
-  __ PushPair(TMP, TypeTestABI::kFunctionTypeArgumentsReg);
-
-  __ LoadUniqueObject(R0, test_cache);
-  __ LoadObject(TMP, Smi::ZoneHandle(zone(), Smi::New(kTypeCheckFromInline)));
-  __ PushPair(TMP, R0);
-  GenerateRuntimeCall(token_pos, deopt_id, kTypeCheckRuntimeEntry, 7, locs);
-  // Pop the parameters supplied to the runtime entry. The result of the
-  // type check runtime call is the checked value.
-  __ Drop(7);
-  __ Pop(TypeTestABI::kInstanceReg);
-  __ Bind(&is_assignable);
-  __ PopPair(TypeTestABI::kFunctionTypeArgumentsReg,
-             TypeTestABI::kInstantiatorTypeArgumentsReg);
-  __ Bind(&is_assignable_fast);
 }
 
 void FlowGraphCompiler::GenerateAssertAssignableViaTypeTestingStub(
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
index 4b4e217..3f867c5 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
@@ -684,59 +684,19 @@
   ASSERT(!token_pos.IsClassifying());
   ASSERT(CheckAssertAssignableTypeTestingABILocations(*locs));
 
-  compiler::Label is_assignable, runtime_call;
-
-  // Generate inline type check, linking to runtime call if not assignable.
-  SubtypeTestCache& test_cache = SubtypeTestCache::ZoneHandle(zone());
-
   if (locs->in(1).IsConstant()) {
     const auto& dst_type = AbstractType::Cast(locs->in(1).constant());
     ASSERT(dst_type.IsFinalized());
 
     if (dst_type.IsTopTypeForSubtyping()) return;  // No code needed.
 
-    if (ShouldUseTypeTestingStubFor(is_optimizing(), dst_type)) {
-      GenerateAssertAssignableViaTypeTestingStub(receiver_type, token_pos,
-                                                 deopt_id, dst_name, locs);
-      return;
-    }
-
-    if (Instance::NullIsAssignableTo(dst_type)) {
-      __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
-      __ j(EQUAL, &is_assignable);
-    }
-
-    // The registers RAX, RCX, RDX are preserved across the call.
-    test_cache = GenerateInlineInstanceof(token_pos, dst_type, &is_assignable,
-                                          &runtime_call);
-
+    GenerateAssertAssignableViaTypeTestingStub(receiver_type, token_pos,
+                                               deopt_id, dst_name, locs);
+    return;
   } else {
     // TODO(dartbug.com/40813): Handle setting up the non-constant case.
     UNREACHABLE();
   }
-
-  __ Bind(&runtime_call);
-  __ PushObject(Object::null_object());  // Make room for the result.
-  __ pushq(TypeTestABI::kInstanceReg);   // Push the source object.
-  // Push the destination type.
-  if (locs->in(1).IsConstant()) {
-    __ PushObject(locs->in(1).constant());
-  } else {
-    // TODO(dartbug.com/40813): Handle setting up the non-constant case.
-    UNREACHABLE();
-  }
-  __ pushq(TypeTestABI::kInstantiatorTypeArgumentsReg);
-  __ pushq(TypeTestABI::kFunctionTypeArgumentsReg);
-  __ PushObject(dst_name);  // Push the name of the destination.
-  __ LoadUniqueObject(RAX, test_cache);
-  __ pushq(RAX);
-  __ PushImmediate(compiler::Immediate(Smi::RawValue(kTypeCheckFromInline)));
-  GenerateRuntimeCall(token_pos, deopt_id, kTypeCheckRuntimeEntry, 7, locs);
-  // Pop the parameters supplied to the runtime entry. The result of the
-  // type check runtime call is the checked value.
-  __ Drop(7);
-  __ popq(TypeTestABI::kInstanceReg);
-  __ Bind(&is_assignable);
 }
 
 void FlowGraphCompiler::GenerateAssertAssignableViaTypeTestingStub(
diff --git a/runtime/vm/compiler/backend/il_arm.cc b/runtime/vm/compiler/backend/il_arm.cc
index df51c7d..8aec81f 100644
--- a/runtime/vm/compiler/backend/il_arm.cc
+++ b/runtime/vm/compiler/backend/il_arm.cc
@@ -759,15 +759,12 @@
   auto const dst_type_loc =
       LocationFixedRegisterOrConstant(dst_type(), TypeTestABI::kDstTypeReg);
 
-  // When using a type testing stub, we want to prevent spilling of the
-  // function/instantiator type argument vectors, since stub preserves them. So
-  // we make this a `kNoCall` summary, even though most other registers can be
-  // modified by the stub. To tell the register allocator about it, we reserve
-  // all the other registers as temporary registers.
+  // We want to prevent spilling of the inputs (e.g. function/instantiator tav),
+  // since TTS preserves them. So we make this a `kNoCall` summary,
+  // even though most other registers can be modified by the stub. To tell the
+  // register allocator about it, we reserve all the other registers as
+  // temporary registers.
   // TODO(http://dartbug.com/32788): Simplify this.
-  const bool using_stub = dst_type_loc.IsConstant() &&
-                          FlowGraphCompiler::ShouldUseTypeTestingStubFor(
-                              opt, AbstractType::Cast(dst_type_loc.constant()));
 
   const intptr_t kNonChangeableInputRegs =
       (1 << TypeTestABI::kInstanceReg) |
@@ -789,14 +786,11 @@
         << kAbiFirstPreservedFpuReg) &
       ~(1 << FpuTMP);
 
-  const intptr_t kNumTemps =
-      using_stub ? (Utils::CountOneBits64(kCpuRegistersToPreserve) +
-                    Utils::CountOneBits64(kFpuRegistersToPreserve))
-                 : 0;
+  const intptr_t kNumTemps = (Utils::CountOneBits64(kCpuRegistersToPreserve) +
+                              Utils::CountOneBits64(kFpuRegistersToPreserve));
 
   LocationSummary* summary = new (zone) LocationSummary(
-      zone, kNumInputs, kNumTemps,
-      using_stub ? LocationSummary::kCallCalleeSafe : LocationSummary::kCall);
+      zone, kNumInputs, kNumTemps, LocationSummary::kCallCalleeSafe);
   summary->set_in(0, Location::RegisterLocation(TypeTestABI::kInstanceReg));
   summary->set_in(1, dst_type_loc);
   summary->set_in(2, Location::RegisterLocation(
@@ -805,23 +799,21 @@
       3, Location::RegisterLocation(TypeTestABI::kFunctionTypeArgumentsReg));
   summary->set_out(0, Location::SameAsFirstInput());
 
-  if (using_stub) {
-    // Let's reserve all registers except for the input ones.
-    intptr_t next_temp = 0;
-    for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) {
-      const bool should_preserve = ((1 << i) & kCpuRegistersToPreserve) != 0;
-      if (should_preserve) {
-        summary->set_temp(next_temp++,
-                          Location::RegisterLocation(static_cast<Register>(i)));
-      }
+  // Let's reserve all registers except for the input ones.
+  intptr_t next_temp = 0;
+  for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) {
+    const bool should_preserve = ((1 << i) & kCpuRegistersToPreserve) != 0;
+    if (should_preserve) {
+      summary->set_temp(next_temp++,
+                        Location::RegisterLocation(static_cast<Register>(i)));
     }
+  }
 
-    for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) {
-      const bool should_preserve = ((1 << i) & kFpuRegistersToPreserve) != 0;
-      if (should_preserve) {
-        summary->set_temp(next_temp++, Location::FpuRegisterLocation(
-                                           static_cast<FpuRegister>(i)));
-      }
+  for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) {
+    const bool should_preserve = ((1 << i) & kFpuRegistersToPreserve) != 0;
+    if (should_preserve) {
+      summary->set_temp(next_temp++, Location::FpuRegisterLocation(
+                                         static_cast<FpuRegister>(i)));
     }
   }
 
diff --git a/runtime/vm/compiler/backend/il_arm64.cc b/runtime/vm/compiler/backend/il_arm64.cc
index c0ec0b2..563bbe7 100644
--- a/runtime/vm/compiler/backend/il_arm64.cc
+++ b/runtime/vm/compiler/backend/il_arm64.cc
@@ -658,15 +658,12 @@
   auto const dst_type_loc =
       LocationFixedRegisterOrConstant(dst_type(), TypeTestABI::kDstTypeReg);
 
-  // When using a type testing stub, we want to prevent spilling of the
-  // function/instantiator type argument vectors, since stub preserves them. So
-  // we make this a `kNoCall` summary, even though most other registers can be
-  // modified by the stub. To tell the register allocator about it, we reserve
-  // all the other registers as temporary registers.
+  // We want to prevent spilling of the inputs (e.g. function/instantiator tav),
+  // since TTS preserves them. So we make this a `kNoCall` summary,
+  // even though most other registers can be modified by the stub. To tell the
+  // register allocator about it, we reserve all the other registers as
+  // temporary registers.
   // TODO(http://dartbug.com/32788): Simplify this.
-  const bool using_stub = dst_type_loc.IsConstant() &&
-                          FlowGraphCompiler::ShouldUseTypeTestingStubFor(
-                              opt, AbstractType::Cast(dst_type_loc.constant()));
 
   const intptr_t kNonChangeableInputRegs =
       (1 << TypeTestABI::kInstanceReg) |
@@ -686,14 +683,11 @@
   const intptr_t kFpuRegistersToPreserve =
       Utils::SignedNBitMask(kNumberOfFpuRegisters) & ~(1l << FpuTMP);
 
-  const intptr_t kNumTemps =
-      using_stub ? (Utils::CountOneBits64(kCpuRegistersToPreserve) +
-                    Utils::CountOneBits64(kFpuRegistersToPreserve))
-                 : 0;
+  const intptr_t kNumTemps = (Utils::CountOneBits64(kCpuRegistersToPreserve) +
+                              Utils::CountOneBits64(kFpuRegistersToPreserve));
 
   LocationSummary* summary = new (zone) LocationSummary(
-      zone, kNumInputs, kNumTemps,
-      using_stub ? LocationSummary::kCallCalleeSafe : LocationSummary::kCall);
+      zone, kNumInputs, kNumTemps, LocationSummary::kCallCalleeSafe);
   summary->set_in(0, Location::RegisterLocation(TypeTestABI::kInstanceReg));
   summary->set_in(1, dst_type_loc);
   summary->set_in(2, Location::RegisterLocation(
@@ -702,23 +696,21 @@
       3, Location::RegisterLocation(TypeTestABI::kFunctionTypeArgumentsReg));
   summary->set_out(0, Location::SameAsFirstInput());
 
-  if (using_stub) {
-    // Let's reserve all registers except for the input ones.
-    intptr_t next_temp = 0;
-    for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) {
-      const bool should_preserve = ((1 << i) & kCpuRegistersToPreserve) != 0;
-      if (should_preserve) {
-        summary->set_temp(next_temp++,
-                          Location::RegisterLocation(static_cast<Register>(i)));
-      }
+  // Let's reserve all registers except for the input ones.
+  intptr_t next_temp = 0;
+  for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) {
+    const bool should_preserve = ((1 << i) & kCpuRegistersToPreserve) != 0;
+    if (should_preserve) {
+      summary->set_temp(next_temp++,
+                        Location::RegisterLocation(static_cast<Register>(i)));
     }
+  }
 
-    for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) {
-      const bool should_preserve = ((1l << i) & kFpuRegistersToPreserve) != 0;
-      if (should_preserve) {
-        summary->set_temp(next_temp++, Location::FpuRegisterLocation(
-                                           static_cast<FpuRegister>(i)));
-      }
+  for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) {
+    const bool should_preserve = ((1l << i) & kFpuRegistersToPreserve) != 0;
+    if (should_preserve) {
+      summary->set_temp(next_temp++, Location::FpuRegisterLocation(
+                                         static_cast<FpuRegister>(i)));
     }
   }
 
diff --git a/runtime/vm/compiler/backend/il_x64.cc b/runtime/vm/compiler/backend/il_x64.cc
index e21744e..70f492c 100644
--- a/runtime/vm/compiler/backend/il_x64.cc
+++ b/runtime/vm/compiler/backend/il_x64.cc
@@ -594,15 +594,12 @@
   auto const dst_type_loc =
       LocationFixedRegisterOrConstant(dst_type(), TypeTestABI::kDstTypeReg);
 
-  // When using a type testing stub, we want to prevent spilling of the
-  // function/instantiator type argument vectors, since stub preserves them. So
-  // we make this a `kNoCall` summary, even though most other registers can be
-  // modified by the stub. To tell the register allocator about it, we reserve
-  // all the other registers as temporary registers.
+  // We want to prevent spilling of the inputs (e.g. function/instantiator tav),
+  // since TTS preserves them. So we make this a `kNoCall` summary,
+  // even though most other registers can be modified by the stub. To tell the
+  // register allocator about it, we reserve all the other registers as
+  // temporary registers.
   // TODO(http://dartbug.com/32788): Simplify this.
-  const bool using_stub = dst_type_loc.IsConstant() &&
-                          FlowGraphCompiler::ShouldUseTypeTestingStubFor(
-                              opt, AbstractType::Cast(dst_type_loc.constant()));
 
   const intptr_t kNonChangeableInputRegs =
       (1 << TypeTestABI::kInstanceReg) |
@@ -621,14 +618,11 @@
   const intptr_t kFpuRegistersToPreserve =
       CallingConventions::kVolatileXmmRegisters & ~(1 << FpuTMP);
 
-  const intptr_t kNumTemps =
-      using_stub ? (Utils::CountOneBits64(kCpuRegistersToPreserve) +
-                    Utils::CountOneBits64(kFpuRegistersToPreserve))
-                 : 0;
+  const intptr_t kNumTemps = (Utils::CountOneBits64(kCpuRegistersToPreserve) +
+                              Utils::CountOneBits64(kFpuRegistersToPreserve));
 
   LocationSummary* summary = new (zone) LocationSummary(
-      zone, kNumInputs, kNumTemps,
-      using_stub ? LocationSummary::kCallCalleeSafe : LocationSummary::kCall);
+      zone, kNumInputs, kNumTemps, LocationSummary::kCallCalleeSafe);
   summary->set_in(0, Location::RegisterLocation(TypeTestABI::kInstanceReg));
   summary->set_in(1, dst_type_loc);
   summary->set_in(2, Location::RegisterLocation(
@@ -637,23 +631,21 @@
       3, Location::RegisterLocation(TypeTestABI::kFunctionTypeArgumentsReg));
   summary->set_out(0, Location::SameAsFirstInput());
 
-  if (using_stub) {
-    // Let's reserve all registers except for the input ones.
-    intptr_t next_temp = 0;
-    for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) {
-      const bool should_preserve = ((1 << i) & kCpuRegistersToPreserve) != 0;
-      if (should_preserve) {
-        summary->set_temp(next_temp++,
-                          Location::RegisterLocation(static_cast<Register>(i)));
-      }
+  // Let's reserve all registers except for the input ones.
+  intptr_t next_temp = 0;
+  for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) {
+    const bool should_preserve = ((1 << i) & kCpuRegistersToPreserve) != 0;
+    if (should_preserve) {
+      summary->set_temp(next_temp++,
+                        Location::RegisterLocation(static_cast<Register>(i)));
     }
+  }
 
-    for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) {
-      const bool should_preserve = ((1 << i) & kFpuRegistersToPreserve) != 0;
-      if (should_preserve) {
-        summary->set_temp(next_temp++, Location::FpuRegisterLocation(
-                                           static_cast<FpuRegister>(i)));
-      }
+  for (intptr_t i = 0; i < kNumberOfFpuRegisters; i++) {
+    const bool should_preserve = ((1 << i) & kFpuRegistersToPreserve) != 0;
+    if (should_preserve) {
+      summary->set_temp(next_temp++, Location::FpuRegisterLocation(
+                                         static_cast<FpuRegister>(i)));
     }
   }
 
diff --git a/runtime/vm/compiler/relocation.cc b/runtime/vm/compiler/relocation.cc
index df7a9dd..d84a679 100644
--- a/runtime/vm/compiler/relocation.cc
+++ b/runtime/vm/compiler/relocation.cc
@@ -434,6 +434,7 @@
     // live in the "vm-isolate" - such as `Type::dynamic_type()`).
     if (destination_.InVMIsolateHeap()) {
       auto object_store = thread_->isolate()->object_store();
+
       if (destination_.raw() == StubCode::DefaultTypeTest().raw()) {
         destination_ = object_store->default_tts_stub();
       } else if (destination_.raw() ==
@@ -445,6 +446,12 @@
         destination_ = object_store->unreachable_tts_stub();
       } else if (destination_.raw() == StubCode::SlowTypeTest().raw()) {
         destination_ = object_store->slow_tts_stub();
+      } else if (destination_.raw() ==
+                 StubCode::NullableTypeParameterTypeTest().raw()) {
+        destination_ = object_store->nullable_type_parameter_tts_stub();
+      } else if (destination_.raw() ==
+                 StubCode::TypeParameterTypeTest().raw()) {
+        destination_ = object_store->type_parameter_tts_stub();
       } else {
         UNREACHABLE();
       }
diff --git a/runtime/vm/compiler/runtime_api.h b/runtime/vm/compiler/runtime_api.h
index 31fa267..4d70b77 100644
--- a/runtime/vm/compiler/runtime_api.h
+++ b/runtime/vm/compiler/runtime_api.h
@@ -883,6 +883,8 @@
  public:
   static word InstanceSize();
   static word NextFieldOffset();
+  static word parameterized_class_id_offset();
+  static word index_offset();
 };
 
 class LibraryPrefix : public AllStatic {
diff --git a/runtime/vm/compiler/runtime_offsets_extracted.h b/runtime/vm/compiler/runtime_offsets_extracted.h
index d29d7b5..1c8cd1c2fd 100644
--- a/runtime/vm/compiler/runtime_offsets_extracted.h
+++ b/runtime/vm/compiler/runtime_offsets_extracted.h
@@ -379,6 +379,9 @@
 static constexpr dart::compiler::target::word Type_type_state_offset = 32;
 static constexpr dart::compiler::target::word Type_nullability_offset = 33;
 static constexpr dart::compiler::target::word
+    TypeParameter_parameterized_class_id_offset = 28;
+static constexpr dart::compiler::target::word TypeParameter_index_offset = 36;
+static constexpr dart::compiler::target::word
     TypeArguments_instantiations_offset = 4;
 static constexpr dart::compiler::target::word TypeArguments_length_offset = 8;
 static constexpr dart::compiler::target::word TypeArguments_nullability_offset =
@@ -887,6 +890,9 @@
 static constexpr dart::compiler::target::word Type_type_state_offset = 60;
 static constexpr dart::compiler::target::word Type_nullability_offset = 61;
 static constexpr dart::compiler::target::word
+    TypeParameter_parameterized_class_id_offset = 56;
+static constexpr dart::compiler::target::word TypeParameter_index_offset = 64;
+static constexpr dart::compiler::target::word
     TypeArguments_instantiations_offset = 8;
 static constexpr dart::compiler::target::word TypeArguments_length_offset = 16;
 static constexpr dart::compiler::target::word TypeArguments_nullability_offset =
@@ -1391,6 +1397,9 @@
 static constexpr dart::compiler::target::word Type_type_state_offset = 32;
 static constexpr dart::compiler::target::word Type_nullability_offset = 33;
 static constexpr dart::compiler::target::word
+    TypeParameter_parameterized_class_id_offset = 28;
+static constexpr dart::compiler::target::word TypeParameter_index_offset = 36;
+static constexpr dart::compiler::target::word
     TypeArguments_instantiations_offset = 4;
 static constexpr dart::compiler::target::word TypeArguments_length_offset = 8;
 static constexpr dart::compiler::target::word TypeArguments_nullability_offset =
@@ -1896,6 +1905,9 @@
 static constexpr dart::compiler::target::word Type_type_state_offset = 60;
 static constexpr dart::compiler::target::word Type_nullability_offset = 61;
 static constexpr dart::compiler::target::word
+    TypeParameter_parameterized_class_id_offset = 56;
+static constexpr dart::compiler::target::word TypeParameter_index_offset = 64;
+static constexpr dart::compiler::target::word
     TypeArguments_instantiations_offset = 8;
 static constexpr dart::compiler::target::word TypeArguments_length_offset = 16;
 static constexpr dart::compiler::target::word TypeArguments_nullability_offset =
@@ -2400,6 +2412,9 @@
 static constexpr dart::compiler::target::word Type_type_state_offset = 32;
 static constexpr dart::compiler::target::word Type_nullability_offset = 33;
 static constexpr dart::compiler::target::word
+    TypeParameter_parameterized_class_id_offset = 28;
+static constexpr dart::compiler::target::word TypeParameter_index_offset = 36;
+static constexpr dart::compiler::target::word
     TypeArguments_instantiations_offset = 4;
 static constexpr dart::compiler::target::word TypeArguments_length_offset = 8;
 static constexpr dart::compiler::target::word TypeArguments_nullability_offset =
@@ -2902,6 +2917,9 @@
 static constexpr dart::compiler::target::word Type_type_state_offset = 60;
 static constexpr dart::compiler::target::word Type_nullability_offset = 61;
 static constexpr dart::compiler::target::word
+    TypeParameter_parameterized_class_id_offset = 56;
+static constexpr dart::compiler::target::word TypeParameter_index_offset = 64;
+static constexpr dart::compiler::target::word
     TypeArguments_instantiations_offset = 8;
 static constexpr dart::compiler::target::word TypeArguments_length_offset = 16;
 static constexpr dart::compiler::target::word TypeArguments_nullability_offset =
@@ -3400,6 +3418,9 @@
 static constexpr dart::compiler::target::word Type_type_state_offset = 32;
 static constexpr dart::compiler::target::word Type_nullability_offset = 33;
 static constexpr dart::compiler::target::word
+    TypeParameter_parameterized_class_id_offset = 28;
+static constexpr dart::compiler::target::word TypeParameter_index_offset = 36;
+static constexpr dart::compiler::target::word
     TypeArguments_instantiations_offset = 4;
 static constexpr dart::compiler::target::word TypeArguments_length_offset = 8;
 static constexpr dart::compiler::target::word TypeArguments_nullability_offset =
@@ -3899,6 +3920,9 @@
 static constexpr dart::compiler::target::word Type_type_state_offset = 60;
 static constexpr dart::compiler::target::word Type_nullability_offset = 61;
 static constexpr dart::compiler::target::word
+    TypeParameter_parameterized_class_id_offset = 56;
+static constexpr dart::compiler::target::word TypeParameter_index_offset = 64;
+static constexpr dart::compiler::target::word
     TypeArguments_instantiations_offset = 8;
 static constexpr dart::compiler::target::word TypeArguments_length_offset = 16;
 static constexpr dart::compiler::target::word TypeArguments_nullability_offset =
@@ -4434,6 +4458,10 @@
 static constexpr dart::compiler::target::word AOT_Type_type_state_offset = 32;
 static constexpr dart::compiler::target::word AOT_Type_nullability_offset = 33;
 static constexpr dart::compiler::target::word
+    AOT_TypeParameter_parameterized_class_id_offset = 28;
+static constexpr dart::compiler::target::word AOT_TypeParameter_index_offset =
+    36;
+static constexpr dart::compiler::target::word
     AOT_TypeArguments_instantiations_offset = 4;
 static constexpr dart::compiler::target::word AOT_TypeArguments_length_offset =
     8;
@@ -4992,6 +5020,10 @@
 static constexpr dart::compiler::target::word AOT_Type_type_state_offset = 60;
 static constexpr dart::compiler::target::word AOT_Type_nullability_offset = 61;
 static constexpr dart::compiler::target::word
+    AOT_TypeParameter_parameterized_class_id_offset = 56;
+static constexpr dart::compiler::target::word AOT_TypeParameter_index_offset =
+    64;
+static constexpr dart::compiler::target::word
     AOT_TypeArguments_instantiations_offset = 8;
 static constexpr dart::compiler::target::word AOT_TypeArguments_length_offset =
     16;
@@ -5555,6 +5587,10 @@
 static constexpr dart::compiler::target::word AOT_Type_type_state_offset = 60;
 static constexpr dart::compiler::target::word AOT_Type_nullability_offset = 61;
 static constexpr dart::compiler::target::word
+    AOT_TypeParameter_parameterized_class_id_offset = 56;
+static constexpr dart::compiler::target::word AOT_TypeParameter_index_offset =
+    64;
+static constexpr dart::compiler::target::word
     AOT_TypeArguments_instantiations_offset = 8;
 static constexpr dart::compiler::target::word AOT_TypeArguments_length_offset =
     16;
@@ -6112,6 +6148,10 @@
 static constexpr dart::compiler::target::word AOT_Type_type_state_offset = 32;
 static constexpr dart::compiler::target::word AOT_Type_nullability_offset = 33;
 static constexpr dart::compiler::target::word
+    AOT_TypeParameter_parameterized_class_id_offset = 28;
+static constexpr dart::compiler::target::word AOT_TypeParameter_index_offset =
+    36;
+static constexpr dart::compiler::target::word
     AOT_TypeArguments_instantiations_offset = 4;
 static constexpr dart::compiler::target::word AOT_TypeArguments_length_offset =
     8;
@@ -6663,6 +6703,10 @@
 static constexpr dart::compiler::target::word AOT_Type_type_state_offset = 60;
 static constexpr dart::compiler::target::word AOT_Type_nullability_offset = 61;
 static constexpr dart::compiler::target::word
+    AOT_TypeParameter_parameterized_class_id_offset = 56;
+static constexpr dart::compiler::target::word AOT_TypeParameter_index_offset =
+    64;
+static constexpr dart::compiler::target::word
     AOT_TypeArguments_instantiations_offset = 8;
 static constexpr dart::compiler::target::word AOT_TypeArguments_length_offset =
     16;
@@ -7219,6 +7263,10 @@
 static constexpr dart::compiler::target::word AOT_Type_type_state_offset = 60;
 static constexpr dart::compiler::target::word AOT_Type_nullability_offset = 61;
 static constexpr dart::compiler::target::word
+    AOT_TypeParameter_parameterized_class_id_offset = 56;
+static constexpr dart::compiler::target::word AOT_TypeParameter_index_offset =
+    64;
+static constexpr dart::compiler::target::word
     AOT_TypeArguments_instantiations_offset = 8;
 static constexpr dart::compiler::target::word AOT_TypeArguments_length_offset =
     16;
diff --git a/runtime/vm/compiler/runtime_offsets_list.h b/runtime/vm/compiler/runtime_offsets_list.h
index 10cfdcd..6f1dcb6 100644
--- a/runtime/vm/compiler/runtime_offsets_list.h
+++ b/runtime/vm/compiler/runtime_offsets_list.h
@@ -249,6 +249,8 @@
   FIELD(Type, type_class_id_offset)                                            \
   FIELD(Type, type_state_offset)                                               \
   FIELD(Type, nullability_offset)                                              \
+  FIELD(TypeParameter, parameterized_class_id_offset)                          \
+  FIELD(TypeParameter, index_offset)                                           \
   FIELD(TypeArguments, instantiations_offset)                                  \
   FIELD(TypeArguments, length_offset)                                          \
   FIELD(TypeArguments, nullability_offset)                                     \
diff --git a/runtime/vm/compiler/stub_code_compiler_arm.cc b/runtime/vm/compiler/stub_code_compiler_arm.cc
index 38ac210..1669dc6 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm.cc
@@ -3223,6 +3223,59 @@
   __ Ret();
 }
 
+static void BuildTypeParameterTypeTestStub(Assembler* assembler,
+                                           bool allow_null) {
+  Label done;
+
+  if (allow_null) {
+    __ CompareObject(TypeTestABI::kInstanceReg, NullObject());
+    __ BranchIf(EQUAL, &done);
+  }
+
+  Label function_type_param;
+  __ ldrh(TypeTestABI::kScratchReg,
+          FieldAddress(TypeTestABI::kDstTypeReg,
+                       TypeParameter::parameterized_class_id_offset()));
+  __ cmp(TypeTestABI::kScratchReg, Operand(kFunctionCid));
+  __ BranchIf(EQUAL, &function_type_param);
+
+  auto handle_case = [&](Register tav) {
+    __ CompareObject(tav, NullObject());
+    __ BranchIf(EQUAL, &done);
+    __ ldrh(
+        TypeTestABI::kScratchReg,
+        FieldAddress(TypeTestABI::kDstTypeReg, TypeParameter::index_offset()));
+    __ add(TypeTestABI::kScratchReg, tav,
+           Operand(TypeTestABI::kScratchReg, LSL, 8));
+    __ ldr(TypeTestABI::kScratchReg,
+           FieldAddress(TypeTestABI::kScratchReg,
+                        target::TypeArguments::InstanceSize()));
+    __ Branch(FieldAddress(TypeTestABI::kScratchReg,
+                           AbstractType::type_test_stub_entry_point_offset()));
+  };
+
+  // Class type parameter: If dynamic we're done, otherwise dereference type
+  // parameter and tail call.
+  handle_case(TypeTestABI::kInstantiatorTypeArgumentsReg);
+
+  // Function type parameter: If dynamic we're done, otherwise dereference type
+  // parameter and tail call.
+  __ Bind(&function_type_param);
+  handle_case(TypeTestABI::kFunctionTypeArgumentsReg);
+
+  __ Bind(&done);
+  __ Ret();
+}
+
+void StubCodeCompiler::GenerateNullableTypeParameterTypeTestStub(
+    Assembler* assembler) {
+  BuildTypeParameterTypeTestStub(assembler, /*allow_null=*/true);
+}
+
+void StubCodeCompiler::GenerateTypeParameterTypeTestStub(Assembler* assembler) {
+  BuildTypeParameterTypeTestStub(assembler, /*allow_null=*/false);
+}
+
 void StubCodeCompiler::GenerateSlowTypeTestStub(Assembler* assembler) {
   Label done, call_runtime;
 
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index afc9b10..6ff81a8 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -3391,6 +3391,63 @@
   __ Ret();
 }
 
+static void BuildTypeParameterTypeTestStub(Assembler* assembler,
+                                           bool allow_null) {
+  Label done;
+
+  if (allow_null) {
+    __ CompareObject(TypeTestABI::kInstanceReg, NullObject());
+    __ BranchIf(EQUAL, &done);
+  }
+
+  Label function_type_param;
+  __ ldr(TypeTestABI::kScratchReg,
+         FieldAddress(TypeTestABI::kDstTypeReg,
+                      TypeParameter::parameterized_class_id_offset()),
+         kUnsignedHalfword);
+  __ cmp(TypeTestABI::kScratchReg, Operand(kFunctionCid));
+  __ BranchIf(EQUAL, &function_type_param);
+
+  auto handle_case = [&](Register tav) {
+    __ CompareObject(tav, NullObject());
+    __ BranchIf(EQUAL, &done);
+    __ ldr(
+        TypeTestABI::kScratchReg,
+        FieldAddress(TypeTestABI::kDstTypeReg, TypeParameter::index_offset()),
+        kUnsignedHalfword);
+    __ add(TypeTestABI::kScratchReg, tav,
+           Operand(TypeTestABI::kScratchReg, LSL, 8));
+    __ ldr(TypeTestABI::kScratchReg,
+           FieldAddress(TypeTestABI::kScratchReg,
+                        target::TypeArguments::InstanceSize()));
+    __ ldr(TypeTestABI::kScratchReg,
+           FieldAddress(TypeTestABI::kScratchReg,
+                        AbstractType::type_test_stub_entry_point_offset()));
+    __ br(TypeTestABI::kScratchReg);
+  };
+
+  // Class type parameter: If dynamic we're done, otherwise dereference type
+  // parameter and tail call.
+  handle_case(TypeTestABI::kInstantiatorTypeArgumentsReg);
+
+  // Function type parameter: If dynamic we're done, otherwise dereference type
+  // parameter and tail call.
+  __ Bind(&function_type_param);
+  handle_case(TypeTestABI::kFunctionTypeArgumentsReg);
+
+  __ Bind(&done);
+  __ Ret();
+}
+
+void StubCodeCompiler::GenerateNullableTypeParameterTypeTestStub(
+    Assembler* assembler) {
+  BuildTypeParameterTypeTestStub(assembler, /*allow_null=*/true);
+}
+
+void StubCodeCompiler::GenerateTypeParameterTypeTestStub(Assembler* assembler) {
+  BuildTypeParameterTypeTestStub(assembler, /*allow_null=*/false);
+}
+
 void StubCodeCompiler::GenerateSlowTypeTestStub(Assembler* assembler) {
   Label done, call_runtime;
 
diff --git a/runtime/vm/compiler/stub_code_compiler_ia32.cc b/runtime/vm/compiler/stub_code_compiler_ia32.cc
index 96bf0ff..9ce50a1 100644
--- a/runtime/vm/compiler/stub_code_compiler_ia32.cc
+++ b/runtime/vm/compiler/stub_code_compiler_ia32.cc
@@ -2630,6 +2630,17 @@
   __ Breakpoint();
 }
 
+void StubCodeCompiler::GenerateNullableTypeParameterTypeTestStub(
+    Assembler* assembler) {
+  // Not implemented on ia32.
+  __ Breakpoint();
+}
+
+void StubCodeCompiler::GenerateTypeParameterTypeTestStub(Assembler* assembler) {
+  // Not implemented on ia32.
+  __ Breakpoint();
+}
+
 void StubCodeCompiler::GenerateSlowTypeTestStub(Assembler* assembler) {
   // Not implemented on ia32.
   __ Breakpoint();
diff --git a/runtime/vm/compiler/stub_code_compiler_x64.cc b/runtime/vm/compiler/stub_code_compiler_x64.cc
index e6566c3..e7011d0 100644
--- a/runtime/vm/compiler/stub_code_compiler_x64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_x64.cc
@@ -3346,6 +3346,56 @@
   __ Ret();
 }
 
+static void BuildTypeParameterTypeTestStub(Assembler* assembler,
+                                           bool allow_null) {
+  Label done;
+
+  if (allow_null) {
+    __ CompareObject(TypeTestABI::kInstanceReg, NullObject());
+    __ BranchIf(EQUAL, &done);
+  }
+
+  Label function_type_param;
+  __ cmpw(FieldAddress(TypeTestABI::kDstTypeReg,
+                       TypeParameter::parameterized_class_id_offset()),
+          Immediate(kFunctionCid));
+  __ BranchIf(EQUAL, &function_type_param);
+
+  auto handle_case = [&](Register tav) {
+    __ CompareObject(tav, NullObject());
+    __ BranchIf(EQUAL, &done);
+    __ movzxw(
+        TypeTestABI::kScratchReg,
+        FieldAddress(TypeTestABI::kDstTypeReg, TypeParameter::index_offset()));
+    __ movq(TypeTestABI::kScratchReg,
+            FieldAddress(tav, TypeTestABI::kScratchReg, TIMES_8,
+                         target::TypeArguments::InstanceSize()));
+    __ jmp(FieldAddress(TypeTestABI::kScratchReg,
+                        AbstractType::type_test_stub_entry_point_offset()));
+  };
+
+  // Class type parameter: If dynamic we're done, otherwise dereference type
+  // parameter and tail call.
+  handle_case(TypeTestABI::kInstantiatorTypeArgumentsReg);
+
+  // Function type parameter: If dynamic we're done, otherwise dereference type
+  // parameter and tail call.
+  __ Bind(&function_type_param);
+  handle_case(TypeTestABI::kFunctionTypeArgumentsReg);
+
+  __ Bind(&done);
+  __ Ret();
+}
+
+void StubCodeCompiler::GenerateNullableTypeParameterTypeTestStub(
+    Assembler* assembler) {
+  BuildTypeParameterTypeTestStub(assembler, /*allow_null=*/true);
+}
+
+void StubCodeCompiler::GenerateTypeParameterTypeTestStub(Assembler* assembler) {
+  BuildTypeParameterTypeTestStub(assembler, /*allow_null=*/false);
+}
+
 void StubCodeCompiler::GenerateSlowTypeTestStub(Assembler* assembler) {
   Label done, call_runtime;
 
diff --git a/runtime/vm/constants_arm.h b/runtime/vm/constants_arm.h
index ea46f78a..2c62815 100644
--- a/runtime/vm/constants_arm.h
+++ b/runtime/vm/constants_arm.h
@@ -326,11 +326,12 @@
   static const Register kInstantiatorTypeArgumentsReg = R2;
   static const Register kFunctionTypeArgumentsReg = R1;
   static const Register kSubtypeTestCacheReg = R3;
+  static const Register kScratchReg = R4;
 
   static const intptr_t kAbiRegisters =
       (1 << kInstanceReg) | (1 << kDstTypeReg) |
       (1 << kInstantiatorTypeArgumentsReg) | (1 << kFunctionTypeArgumentsReg) |
-      (1 << kSubtypeTestCacheReg);
+      (1 << kSubtypeTestCacheReg) | (1 << kScratchReg);
 
   // For call to InstanceOfStub.
   static const Register kResultReg = R0;
diff --git a/runtime/vm/constants_arm64.h b/runtime/vm/constants_arm64.h
index 2ce6ac37..1c71baa 100644
--- a/runtime/vm/constants_arm64.h
+++ b/runtime/vm/constants_arm64.h
@@ -158,11 +158,12 @@
   static const Register kInstantiatorTypeArgumentsReg = R2;
   static const Register kFunctionTypeArgumentsReg = R1;
   static const Register kSubtypeTestCacheReg = R3;
+  static const Register kScratchReg = R4;
 
   static const intptr_t kAbiRegisters =
       (1 << kInstanceReg) | (1 << kDstTypeReg) |
       (1 << kInstantiatorTypeArgumentsReg) | (1 << kFunctionTypeArgumentsReg) |
-      (1 << kSubtypeTestCacheReg);
+      (1 << kSubtypeTestCacheReg) | (1 << kScratchReg);
 
   // For call to InstanceOfStub.
   static const Register kResultReg = R0;
diff --git a/runtime/vm/constants_x64.h b/runtime/vm/constants_x64.h
index d696eac..218823e 100644
--- a/runtime/vm/constants_x64.h
+++ b/runtime/vm/constants_x64.h
@@ -148,11 +148,12 @@
   static const Register kInstantiatorTypeArgumentsReg = RDX;
   static const Register kFunctionTypeArgumentsReg = RCX;
   static const Register kSubtypeTestCacheReg = R9;
+  static const Register kScratchReg = RSI;
 
   static const intptr_t kAbiRegisters =
       (1 << kInstanceReg) | (1 << kDstTypeReg) |
       (1 << kInstantiatorTypeArgumentsReg) | (1 << kFunctionTypeArgumentsReg) |
-      (1 << kSubtypeTestCacheReg);
+      (1 << kSubtypeTestCacheReg) | (1 << kScratchReg);
 
   // For call to InstanceOfStub.
   static const Register kResultReg = RAX;
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index f399b23..46144d1b 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -8387,6 +8387,14 @@
   bool IsFunctionTypeParameter() const {
     return parameterized_function() != Function::null();
   }
+
+  static intptr_t parameterized_class_id_offset() {
+    return OFFSET_OF(TypeParameterLayout, parameterized_class_id_);
+  }
+  static intptr_t index_offset() {
+    return OFFSET_OF(TypeParameterLayout, index_);
+  }
+
   StringPtr name() const { return raw_ptr()->name_; }
   intptr_t index() const { return raw_ptr()->index_; }
   void set_index(intptr_t value) const;
diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h
index 1314622..f8046bd 100644
--- a/runtime/vm/object_store.h
+++ b/runtime/vm/object_store.h
@@ -212,6 +212,8 @@
   RW(Code, default_tts_stub)                                                   \
   RW(Code, default_nullable_tts_stub)                                          \
   RW(Code, top_type_tts_stub)                                                  \
+  RW(Code, nullable_type_parameter_tts_stub)                                   \
+  RW(Code, type_parameter_tts_stub)                                            \
   RW(Code, unreachable_tts_stub)                                               \
   RW(Code, slow_tts_stub)                                                      \
   RW(Array, dispatch_table_code_entries)                                       \
@@ -251,6 +253,8 @@
   DO(default_tts_stub, DefaultTypeTest)                                        \
   DO(default_nullable_tts_stub, DefaultNullableTypeTest)                       \
   DO(top_type_tts_stub, TopTypeTypeTest)                                       \
+  DO(nullable_type_parameter_tts_stub, NullableTypeParameterTypeTest)          \
+  DO(type_parameter_tts_stub, TypeParameterTypeTest)                           \
   DO(unreachable_tts_stub, UnreachableTypeTest)                                \
   DO(slow_tts_stub, SlowTypeTest)                                              \
   DO(write_barrier_wrappers_stub, WriteBarrierWrappers)                        \
diff --git a/runtime/vm/service.h b/runtime/vm/service.h
index b4f7acd..a3cf970 100644
--- a/runtime/vm/service.h
+++ b/runtime/vm/service.h
@@ -14,8 +14,8 @@
 
 namespace dart {
 
-#define SERVICE_PROTOCOL_MAJOR_VERSION 4
-#define SERVICE_PROTOCOL_MINOR_VERSION 0
+#define SERVICE_PROTOCOL_MAJOR_VERSION 3
+#define SERVICE_PROTOCOL_MINOR_VERSION 39
 
 class Array;
 class EmbedderServiceHandler;
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index 5eeb755..69dde47 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -1,8 +1,8 @@
-# Dart VM Service Protocol 4.0 
+# Dart VM Service Protocol 3.39
 
 > Please post feedback to the [observatory-discuss group][discuss-list]
 
-This document describes of _version 4.0_ of the Dart VM Service Protocol. This
+This document describes of _version 3.39_ 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.
@@ -3834,6 +3834,6 @@
 3.36 | Added `getProcessMemoryUsage` RPC and `ProcessMemoryUsage` and `ProcessMemoryItem` objects.
 3.37 | Added `getWebSocketTarget` RPC and `WebSocketTarget` object.
 3.38 | Added `isSystemIsolate` property to `@Isolate` and `Isolate`, `isSystemIsolateGroup` property to `@IsolateGroup` and `IsolateGroup`, and properties `systemIsolates` and `systemIsolateGroups` to `VM`.
-4.0 | Removed the following deprecated RPCs and objects: `getClientName`, `getWebSocketTarget`, `setClientName`, `requireResumeApproval`, `ClientName`, and `WebSocketTarget`.
+3.39 | Removed the following deprecated RPCs and objects: `getClientName`, `getWebSocketTarget`, `setClientName`, `requireResumeApproval`, `ClientName`, and `WebSocketTarget`.
 
 [discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss
diff --git a/runtime/vm/stub_code_list.h b/runtime/vm/stub_code_list.h
index f759ea8..e38740c 100644
--- a/runtime/vm/stub_code_list.h
+++ b/runtime/vm/stub_code_list.h
@@ -70,6 +70,8 @@
   V(DefaultNullableTypeTest)                                                   \
   V(TopTypeTypeTest)                                                           \
   V(UnreachableTypeTest)                                                       \
+  V(TypeParameterTypeTest)                                                     \
+  V(NullableTypeParameterTypeTest)                                             \
   V(SlowTypeTest)                                                              \
   V(LazySpecializeTypeTest)                                                    \
   V(LazySpecializeNullableTypeTest)                                            \
diff --git a/runtime/vm/type_testing_stubs.cc b/runtime/vm/type_testing_stubs.cc
index fe40775..8f5f57c 100644
--- a/runtime/vm/type_testing_stubs.cc
+++ b/runtime/vm/type_testing_stubs.cc
@@ -91,10 +91,11 @@
 CodePtr TypeTestingStubGenerator::DefaultCodeForType(
     const AbstractType& type,
     bool lazy_specialize /* = true */) {
+  auto isolate = Isolate::Current();
+
   if (type.IsTypeRef()) {
-    return Isolate::Current()->null_safety()
-               ? StubCode::DefaultTypeTest().raw()
-               : StubCode::DefaultNullableTypeTest().raw();
+    return isolate->null_safety() ? StubCode::DefaultTypeTest().raw()
+                                  : StubCode::DefaultNullableTypeTest().raw();
   }
 
   // During bootstrapping we have no access to stubs yet, so we'll just return
@@ -109,8 +110,16 @@
   if (type.IsTopTypeForSubtyping()) {
     return StubCode::TopTypeTypeTest().raw();
   }
+  if (type.IsTypeParameter()) {
+    const bool nullable = Instance::NullIsAssignableTo(type);
+    if (nullable) {
+      return StubCode::NullableTypeParameterTypeTest().raw();
+    } else {
+      return StubCode::TypeParameterTypeTest().raw();
+    }
+  }
 
-  if (type.IsType() || type.IsTypeParameter()) {
+  if (type.IsType()) {
     const bool should_specialize = !FLAG_precompiled_mode && lazy_specialize;
     const bool nullable = Instance::NullIsAssignableTo(type);
     if (should_specialize) {
@@ -144,7 +153,7 @@
 #if !defined(TARGET_ARCH_IA32)
   ASSERT(StubCode::HasBeenInitialized());
 
-  if (type.IsTypeRef()) {
+  if (type.IsTypeRef() || type.IsTypeParameter()) {
     return TypeTestingStubGenerator::DefaultCodeForType(
         type, /*lazy_specialize=*/false);
   }
diff --git a/runtime/vm/type_testing_stubs_test.cc b/runtime/vm/type_testing_stubs_test.cc
index f6afdad..0a218cb 100644
--- a/runtime/vm/type_testing_stubs_test.cc
+++ b/runtime/vm/type_testing_stubs_test.cc
@@ -75,8 +75,14 @@
   arguments.SetAt(5, dst_type);
 
   // Ensure we have a) uninitialized TTS b) no/empty SubtypeTestCache.
-  dst_type.SetTypeTestingStub(StubCode::LazySpecializeTypeTest());
-  EXPECT(dst_type.type_test_stub() == StubCode::LazySpecializeTypeTest().raw());
+  auto& instantiated_dst_type = AbstractType::Handle(dst_type.raw());
+  if (dst_type.IsTypeParameter()) {
+    instantiated_dst_type = TypeParameter::Cast(dst_type).GetFromTypeArguments(
+        instantiator_tav, function_tav);
+  }
+  instantiated_dst_type.SetTypeTestingStub(StubCode::LazySpecializeTypeTest());
+  EXPECT(instantiated_dst_type.type_test_stub() ==
+         StubCode::LazySpecializeTypeTest().raw());
   EXPECT(pool.ObjectAt(kSubtypeTestCacheIndex) == Object::null());
 
   auto& result = Object::Handle();
@@ -92,7 +98,7 @@
   result = DartEntry::InvokeCode(invoke_tts, arguments_descriptor, arguments,
                                  thread);
   stc ^= pool.ObjectAt(kSubtypeTestCacheIndex);
-  tts = dst_type.type_test_stub();
+  tts = instantiated_dst_type.type_test_stub();
   if (!result.IsError()) {
     EXPECT(tts.raw() != StubCode::LazySpecializeTypeTest().raw());
   }
@@ -102,7 +108,7 @@
   result2 = DartEntry::InvokeCode(invoke_tts, arguments_descriptor, arguments,
                                   thread);
   stc2 ^= pool.ObjectAt(kSubtypeTestCacheIndex);
-  tts2 = dst_type.type_test_stub();
+  tts2 = instantiated_dst_type.type_test_stub();
   abi_regs_modified ^= abi_regs_modified_box.At(0);
   rest_regs_modified ^= rest_regs_modified_box.At(0);
   EXPECT(result2.IsError() || !abi_regs_modified.IsNull());
@@ -114,18 +120,13 @@
   // SubtypeTestCache
   // (This is to simulate AOT where we don't use lazy specialization but
   // precompile the TTS)
-  auto& dst_type_to_specialize = AbstractType::Handle(dst_type.raw());
-  if (dst_type_to_specialize.IsTypeParameter()) {
-    dst_type_to_specialize = TypeParameter::Cast(dst_type).GetFromTypeArguments(
-        instantiator_tav, function_tav);
-  }
-  TypeTestingStubGenerator::SpecializeStubFor(thread, dst_type_to_specialize);
-  tts = dst_type.type_test_stub();
+  TypeTestingStubGenerator::SpecializeStubFor(thread, instantiated_dst_type);
+  tts = instantiated_dst_type.type_test_stub();
 
   result2 = DartEntry::InvokeCode(invoke_tts, arguments_descriptor, arguments,
                                   thread);
   stc2 ^= pool.ObjectAt(kSubtypeTestCacheIndex);
-  tts2 = dst_type.type_test_stub();
+  tts2 = instantiated_dst_type.type_test_stub();
   abi_regs_modified ^= abi_regs_modified_box.At(0);
   rest_regs_modified ^= rest_regs_modified_box.At(0);
   EXPECT(result2.IsError() || !abi_regs_modified.IsNull());
@@ -664,6 +665,50 @@
              ExpectFailedViaTTS);
 }
 
+ISOLATE_UNIT_TEST_CASE(TTS_TypeParameter) {
+  const char* kScript =
+      R"(
+          class A<T> {
+            T test(dynamic x) => x as T;
+          }
+          H genericFun<H>(dynamic x) => x as H;
+
+          createAInt() => A<int>();
+          createAString() => A<String>();
+  )";
+
+  const auto& root_library = Library::Handle(LoadTestScript(kScript));
+  const auto& class_a = Class::Handle(GetClass(root_library, "A"));
+  const auto& fun_generic =
+      Function::Handle(GetFunction(root_library, "genericFun"));
+
+  const auto& dst_type_t =
+      TypeParameter::Handle(GetClassTypeParameter(class_a, "T"));
+  const auto& dst_type_h =
+      TypeParameter::Handle(GetFunctionTypeParameter(fun_generic, "H"));
+
+  const auto& aint = Object::Handle(Invoke(root_library, "createAInt"));
+  const auto& astring = Object::Handle(Invoke(root_library, "createAString"));
+
+  const auto& int_tav =
+      TypeArguments::Handle(Instance::Cast(aint).GetTypeArguments());
+  const auto& string_tav =
+      TypeArguments::Handle(Instance::Cast(astring).GetTypeArguments());
+
+  const auto& int_instance = Integer::Handle(Integer::New(1));
+  const auto& string_instance = String::Handle(String::New("foo"));
+
+  RunTTSTest(int_instance, dst_type_t, int_tav, string_tav,
+             ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
+  RunTTSTest(string_instance, dst_type_t, int_tav, string_tav,
+             ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
+
+  RunTTSTest(string_instance, dst_type_h, int_tav, string_tav,
+             ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
+  RunTTSTest(int_instance, dst_type_h, int_tav, string_tav,
+             ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
+}
+
 }  // namespace dart
 
 #endif  // defined(TARGET_ARCH_ARM64) ||  defined(TARGET_ARCH_ARM) ||          \
diff --git a/sdk/lib/_http/http.dart b/sdk/lib/_http/http.dart
index 6e80086..151dae4 100644
--- a/sdk/lib/_http/http.dart
+++ b/sdk/lib/_http/http.dart
@@ -868,7 +868,8 @@
 }
 
 /**
- * A MIME/IANA media type used as the value of the [contentTypeHeader] header.
+ * A MIME/IANA media type used as the value of the
+ * [HttpHeaders.contentTypeHeader] header.
  *
  * A [ContentType] is immutable.
  */
diff --git a/sdk/lib/convert/json.dart b/sdk/lib/convert/json.dart
index 6d897a0..e55bc2a 100644
--- a/sdk/lib/convert/json.dart
+++ b/sdk/lib/convert/json.dart
@@ -64,7 +64,7 @@
 /// a local variable shadows the [json] constant.
 const JsonCodec json = JsonCodec();
 
-/// Converts [value] to a JSON string.
+/// Converts [object] to a JSON string.
 ///
 /// If value contains objects that are not directly encodable to a JSON
 /// string (a value that is not a number, boolean, string, null, list or a map
diff --git a/sdk/lib/core/map.dart b/sdk/lib/core/map.dart
index a92d08d..cbb1608 100644
--- a/sdk/lib/core/map.dart
+++ b/sdk/lib/core/map.dart
@@ -83,7 +83,7 @@
    * Creates an identity map with the default implementation, [LinkedHashMap].
    *
    * An identity map uses [identical] for equality and [identityHashCode]
-   * for hash codes of keys instead of the intrinsic [Object.operator==] and
+   * for hash codes of keys instead of the intrinsic [Object.==] and
    * [Object.hashCode] of the keys.
    *
    * The returned map allows `null` as a key.
diff --git a/sdk/lib/core/regexp.dart b/sdk/lib/core/regexp.dart
index b75882d..ca5309f 100644
--- a/sdk/lib/core/regexp.dart
+++ b/sdk/lib/core/regexp.dart
@@ -152,7 +152,7 @@
    * character is a line terminator. When true, then the "." character will
    * match any single character including line terminators.
    *
-   * This feature is distinct from [isMultiline], as they affect the behavior
+   * This feature is distinct from [isMultiLine], as they affect the behavior
    * of different pattern characters, and so they can be used together or
    * separately.
    */
diff --git a/sdk/lib/io/socket.dart b/sdk/lib/io/socket.dart
index b5e814f..d2a0feb 100644
--- a/sdk/lib/io/socket.dart
+++ b/sdk/lib/io/socket.dart
@@ -176,8 +176,8 @@
    * valid UTF-8 encoded file path.
    *
    * If [type] is omitted, the [rawAddress] must have a length of either 4 or
-   * 16, in which case the type defaults to [InternetAddress.IPv4] or
-   * [InternetAddress.IPv6] respectively.
+   * 16, in which case the type defaults to [InternetAddressType.IPv4] or
+   * [InternetAddressType.IPv6] respectively.
    */
   external factory InternetAddress.fromRawAddress(Uint8List rawAddress,
       {@Since("2.8") InternetAddressType? type});
@@ -472,24 +472,24 @@
 /// [RawSocket.setRawOption] to set customize the behaviour of the underlying
 /// socket.
 ///
-/// It allows for fine grained control of the socket options, and its values will
-/// be passed to the underlying platform's implementation of setsockopt and
+/// It allows for fine grained control of the socket options, and its values
+/// will be passed to the underlying platform's implementation of setsockopt and
 /// getsockopt.
 @Since("2.2")
 class RawSocketOption {
   /// Creates a RawSocketOption for getRawOption andSetRawOption.
   ///
   /// The level and option arguments correspond to level and optname arguments
-  /// on the get/setsockopt native calls.
+  /// on the getsockopt and setsockopt native calls.
   ///
   /// The value argument and its length correspond to the optval and length
   /// arguments on the native call.
   ///
-  /// For a [getRawOption] call, the value parameter will be updated after a
-  /// successful call (although its length will not be changed).
+  /// For a [RawSocket.getRawOption] call, the value parameter will be updated
+  /// after a successful call (although its length will not be changed).
   ///
-  /// For a [setRawOption] call, the value parameter will be used set the
-  /// option.
+  /// For a [RawSocket.setRawOption] call, the value parameter will be used set
+  /// the option.
   const RawSocketOption(this.level, this.option, this.value);
 
   /// Convenience constructor for creating an int based RawSocketOption.
diff --git a/sdk/lib/io/stdio.dart b/sdk/lib/io/stdio.dart
index a0034c6..9fc8401 100644
--- a/sdk/lib/io/stdio.dart
+++ b/sdk/lib/io/stdio.dart
@@ -41,7 +41,7 @@
    * Blocks until a full line is available.
    *
    * Lines my be terminated by either `<CR><LF>` or `<LF>`. On Windows in cases
-   * where the [stdioType] of stdin is [StdioType.termimal] the terminator may
+   * where the [stdioType] of stdin is [StdioType.terminal] the terminator may
    * also be a single `<CR>`.
    *
    * Input bytes are converted to a string by [encoding].
diff --git a/sdk/lib/js/js.dart b/sdk/lib/js/js.dart
index 26a6141..c97d968 100644
--- a/sdk/lib/js/js.dart
+++ b/sdk/lib/js/js.dart
@@ -229,5 +229,5 @@
 ///
 /// See [allowInterop].
 ///
-/// When called from Dart, [null] will be passed as the first argument.
+/// When called from Dart, `null` will be passed as the first argument.
 external Function allowInteropCaptureThis(Function f);
diff --git a/tools/VERSION b/tools/VERSION
index 54f30f2..7aea38a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 10
 PATCH 0
-PRERELEASE 108
+PRERELEASE 109
 PRERELEASE_PATCH 0
\ No newline at end of file