Version 2.16.0-69.0.dev

Merge commit '663357209c3c5f3e1a68ffb3e1320801bc192a7d' into 'dev'
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index 6f24604..a703267 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -1897,6 +1897,24 @@
   /// simplest) way, but prevents the user from being able to type into `stdin`.
   final String? console;
 
+  /// An optional tool to run instead of "dart".
+  ///
+  /// In combination with [customToolReplacesArgs] allows invoking a custom
+  /// tool instead of "dart" to launch scripts/tests. The custom tool must be
+  /// completely compatible with the tool/command it is replacing.
+  ///
+  /// This field should be a full absolute path if the tool may not be available
+  /// in `PATH`.
+  final String? customTool;
+
+  /// The number of arguments to delete from the beginning of the argument list
+  /// when invoking [customTool].
+  ///
+  /// For example, setting [customTool] to `dart_test` and
+  /// `customToolReplacesArgs` to `2` for a test run would invoke
+  /// `dart_test foo_test.dart` instead of `dart run test:test foo_test.dart`.
+  final int? customToolReplacesArgs;
+
   DartLaunchRequestArguments({
     this.noDebug,
     required this.program,
@@ -1905,6 +1923,8 @@
     this.toolArgs,
     this.console,
     this.enableAsserts,
+    this.customTool,
+    this.customToolReplacesArgs,
     Object? restart,
     String? name,
     String? cwd,
@@ -1934,6 +1954,8 @@
         vmServicePort = obj['vmServicePort'] as int?,
         console = obj['console'] as String?,
         enableAsserts = obj['enableAsserts'] as bool?,
+        customTool = obj['customTool'] as String?,
+        customToolReplacesArgs = obj['customToolReplacesArgs'] as int?,
         super.fromMap(obj);
 
   @override
@@ -1946,6 +1968,9 @@
         if (vmServicePort != null) 'vmServicePort': vmServicePort,
         if (console != null) 'console': console,
         if (enableAsserts != null) 'enableAsserts': enableAsserts,
+        if (customTool != null) 'customTool': customTool,
+        if (customToolReplacesArgs != null)
+          'customToolReplacesArgs': customToolReplacesArgs,
       };
 
   static DartLaunchRequestArguments fromJson(Map<String, Object?> obj) =>
diff --git a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
index 3560867..9beae08 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_cli_adapter.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
+import 'dart:math' as math;
 
 import 'package:path/path.dart' as path;
 import 'package:pedantic/pedantic.dart';
@@ -76,7 +77,6 @@
   /// breakpoints, and resume.
   Future<void> launchImpl() async {
     final args = this.args as DartLaunchRequestArguments;
-    final vmPath = Platform.resolvedExecutable;
     File? vmServiceInfoFile;
 
     final debug = !(args.noDebug ?? false);
@@ -101,6 +101,14 @@
       // editor-spawned debug sessions.
       if (args.enableAsserts ?? true) '--enable-asserts',
     ];
+
+    // Handle customTool and deletion of any arguments for it.
+    final executable = args.customTool ?? Platform.resolvedExecutable;
+    final removeArgs = args.customToolReplacesArgs;
+    if (args.customTool != null && removeArgs != null) {
+      vmArgs.removeRange(0, math.min(removeArgs, vmArgs.length));
+    }
+
     final processArgs = [
       ...vmArgs,
       ...?args.toolArgs,
@@ -127,9 +135,14 @@
     // TODO(dantup): Support passing env to both of these.
 
     if (terminalKind != null) {
-      await launchInEditorTerminal(debug, terminalKind, vmPath, processArgs);
+      await launchInEditorTerminal(
+        debug,
+        terminalKind,
+        executable,
+        processArgs,
+      );
     } else {
-      await launchAsProcess(vmPath, processArgs);
+      await launchAsProcess(executable, processArgs);
     }
 
     // Delay responding until the debugger is connected.
@@ -166,11 +179,11 @@
   Future<void> launchInEditorTerminal(
     bool debug,
     String terminalKind,
-    String vmPath,
+    String executable,
     List<String> processArgs,
   ) async {
     final args = this.args as DartLaunchRequestArguments;
-    logger?.call('Spawning $vmPath with $processArgs in ${args.cwd}'
+    logger?.call('Spawning $executable with $processArgs in ${args.cwd}'
         ' via client ${terminalKind} terminal');
 
     // runInTerminal is a DAP request that goes from server-to-client that
@@ -179,7 +192,7 @@
     // for debugging will rely on the process writing the service-info file that
     // we can detect with the normal watching code.
     final requestArgs = RunInTerminalRequestArguments(
-      args: [vmPath, ...processArgs],
+      args: [executable, ...processArgs],
       cwd: args.cwd ?? path.dirname(args.program),
       kind: terminalKind,
       title: args.name ?? 'Dart',
@@ -210,10 +223,13 @@
   ///
   /// Output to `stdout`/`stderr` will be sent to the editor using
   /// [OutputEvent]s.
-  Future<void> launchAsProcess(String vmPath, List<String> processArgs) async {
-    logger?.call('Spawning $vmPath with $processArgs in ${args.cwd}');
+  Future<void> launchAsProcess(
+    String executable,
+    List<String> processArgs,
+  ) async {
+    logger?.call('Spawning $executable with $processArgs in ${args.cwd}');
     final process = await Process.start(
-      vmPath,
+      executable,
       processArgs,
       workingDirectory: args.cwd,
     );
diff --git a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
index 2a63b38..5b0ebfd 100644
--- a/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart_test_adapter.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
+import 'dart:math' as math;
 
 import 'package:pedantic/pedantic.dart';
 import 'package:vm_service/vm_service.dart' as vm;
@@ -72,7 +73,6 @@
   /// breakpoints, and resume.
   Future<void> launchImpl() async {
     final args = this.args as DartLaunchRequestArguments;
-    final vmPath = Platform.resolvedExecutable;
     File? vmServiceInfoFile;
 
     final debug = !(args.noDebug ?? false);
@@ -110,6 +110,14 @@
       '-r',
       'json',
     ];
+
+    // Handle customTool and deletion of any arguments for it.
+    final executable = args.customTool ?? Platform.resolvedExecutable;
+    final removeArgs = args.customToolReplacesArgs;
+    if (args.customTool != null && removeArgs != null) {
+      vmArgs.removeRange(0, math.min(removeArgs, vmArgs.length));
+    }
+
     final processArgs = [
       ...vmArgs,
       ...?args.toolArgs,
@@ -117,11 +125,19 @@
       ...?args.args,
     ];
 
-    // TODO(dantup): Support passing env to both of these.
+    // TODO(dantup): Support passing env.
 
-    logger?.call('Spawning $vmPath with $processArgs in ${args.cwd}');
+    await launchAsProcess(executable, processArgs);
+  }
+
+  /// Launches the test script as a process controlled by the debug adapter.
+  Future<void> launchAsProcess(
+    String executable,
+    List<String> processArgs,
+  ) async {
+    logger?.call('Spawning $executable with $processArgs in ${args.cwd}');
     final process = await Process.start(
-      vmPath,
+      executable,
       processArgs,
       workingDirectory: args.cwd,
     );
diff --git a/pkg/dds/lib/src/dap/isolate_manager.dart b/pkg/dds/lib/src/dap/isolate_manager.dart
index 6d678dc..cbf2087 100644
--- a/pkg/dds/lib/src/dap/isolate_manager.dart
+++ b/pkg/dds/lib/src/dap/isolate_manager.dart
@@ -939,7 +939,7 @@
     final fileUri = Uri.file(filePath);
 
     // Track how many segments from the path are from the lib folder to the
-    // libary that will need to be removed later.
+    // library that will need to be removed later.
     final libraryPathSegments = uri.pathSegments.length - 1;
 
     // It should never be the case that the returned value doesn't have at
diff --git a/pkg/dds/test/dap/dart_cli_test.dart b/pkg/dds/test/dap/dart_cli_test.dart
new file mode 100644
index 0000000..26e56c5
--- /dev/null
+++ b/pkg/dds/test/dap/dart_cli_test.dart
@@ -0,0 +1,79 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:dds/dap.dart';
+import 'package:test/test.dart';
+
+import 'mocks.dart';
+
+main() {
+  group('dart cli adapter', () {
+    test('includes toolArgs', () async {
+      final adapter = MockDartCliDebugAdapter();
+      final responseCompleter = Completer<void>();
+      final request = MockRequest();
+      final args = DartLaunchRequestArguments(
+        program: 'foo.dart',
+        toolArgs: ['tool_arg'],
+        noDebug: true,
+      );
+
+      await adapter.configurationDoneRequest(request, null, () {});
+      await adapter.launchRequest(request, args, responseCompleter.complete);
+      await responseCompleter.future;
+
+      expect(adapter.executable, equals(Platform.resolvedExecutable));
+      expect(adapter.processArgs, contains('tool_arg'));
+    });
+
+    group('includes customTool', () {
+      test('with no args replaced', () async {
+        final adapter = MockDartCliDebugAdapter();
+        final responseCompleter = Completer<void>();
+        final request = MockRequest();
+        final args = DartLaunchRequestArguments(
+          program: 'foo.dart',
+          customTool: '/custom/dart',
+          noDebug: true,
+          enableAsserts: true, // to check args are still passed through
+        );
+
+        await adapter.configurationDoneRequest(request, null, () {});
+        await adapter.launchRequest(request, args, responseCompleter.complete);
+        await responseCompleter.future;
+
+        expect(adapter.executable, equals('/custom/dart'));
+        // args should be in-tact
+        expect(adapter.processArgs, contains('--enable-asserts'));
+      });
+
+      test('with all args replaced', () async {
+        final adapter = MockDartCliDebugAdapter();
+        final responseCompleter = Completer<void>();
+        final request = MockRequest();
+        final args = DartLaunchRequestArguments(
+          program: 'foo.dart',
+          customTool: '/custom/dart',
+          customToolReplacesArgs: 9999, // replaces all built-in args
+          noDebug: true,
+          enableAsserts: true, // should not be in args
+          toolArgs: ['tool_args'], // should still be in args
+        );
+
+        await adapter.configurationDoneRequest(request, null, () {});
+        await adapter.launchRequest(request, args, responseCompleter.complete);
+        await responseCompleter.future;
+
+        expect(adapter.executable, equals('/custom/dart'));
+        // normal built-in args are replaced by customToolReplacesArgs, but
+        // user-provided toolArgs are not.
+        expect(adapter.processArgs, isNot(contains('--enable-asserts')));
+        expect(adapter.processArgs, contains('tool_args'));
+      });
+    });
+  });
+}
diff --git a/pkg/dds/test/dap/dart_test_test.dart b/pkg/dds/test/dap/dart_test_test.dart
new file mode 100644
index 0000000..eac0a34
--- /dev/null
+++ b/pkg/dds/test/dap/dart_test_test.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:dds/dap.dart';
+import 'package:test/test.dart';
+
+import 'mocks.dart';
+
+main() {
+  group('dart test adapter', () {
+    test('includes toolArgs', () async {
+      final adapter = MockDartTestDebugAdapter();
+      final responseCompleter = Completer<void>();
+      final request = MockRequest();
+      final args = DartLaunchRequestArguments(
+        program: 'foo.dart',
+        toolArgs: ['tool_arg'],
+        noDebug: true,
+      );
+
+      await adapter.configurationDoneRequest(request, null, () {});
+      await adapter.launchRequest(request, args, responseCompleter.complete);
+      await responseCompleter.future;
+
+      expect(adapter.executable, equals(Platform.resolvedExecutable));
+      expect(adapter.processArgs, containsAllInOrder(['run', 'test:test']));
+      expect(adapter.processArgs, contains('tool_arg'));
+    });
+
+    group('includes customTool', () {
+      test('with no args replaced', () async {
+        final adapter = MockDartTestDebugAdapter();
+        final responseCompleter = Completer<void>();
+        final request = MockRequest();
+        final args = DartLaunchRequestArguments(
+          program: 'foo.dart',
+          customTool: '/custom/dart',
+          noDebug: true,
+        );
+
+        await adapter.configurationDoneRequest(request, null, () {});
+        await adapter.launchRequest(request, args, responseCompleter.complete);
+        await responseCompleter.future;
+
+        expect(adapter.executable, equals('/custom/dart'));
+        // args should be in-tact
+        expect(adapter.processArgs, containsAllInOrder(['run', 'test:test']));
+      });
+
+      test('with all args replaced', () async {
+        final adapter = MockDartTestDebugAdapter();
+        final responseCompleter = Completer<void>();
+        final request = MockRequest();
+        final args = DartLaunchRequestArguments(
+          program: 'foo.dart',
+          customTool: '/custom/dart',
+          customToolReplacesArgs: 9999, // replaces all built-in args
+          noDebug: true,
+          toolArgs: ['tool_args'], // should still be in args
+        );
+
+        await adapter.configurationDoneRequest(request, null, () {});
+        await adapter.launchRequest(request, args, responseCompleter.complete);
+        await responseCompleter.future;
+
+        expect(adapter.executable, equals('/custom/dart'));
+        // normal built-in args are replaced by customToolReplacesArgs, but
+        // user-provided toolArgs are not.
+        expect(
+          adapter.processArgs,
+          isNot(containsAllInOrder(['run', 'test:test'])),
+        );
+        expect(adapter.processArgs, contains('tool_args'));
+      });
+    });
+  });
+}
diff --git a/pkg/dds/test/dap/mocks.dart b/pkg/dds/test/dap/mocks.dart
new file mode 100644
index 0000000..954f25d
--- /dev/null
+++ b/pkg/dds/test/dap/mocks.dart
@@ -0,0 +1,97 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:dds/dap.dart';
+import 'package:dds/src/dap/adapters/dart_cli_adapter.dart';
+import 'package:dds/src/dap/adapters/dart_test_adapter.dart';
+
+/// A [DartCliDebugAdapter] that captures what process/args will be launched.
+class MockDartCliDebugAdapter extends DartCliDebugAdapter {
+  final StreamSink<List<int>> stdin;
+  final Stream<List<int>> stdout;
+
+  late bool launchedInTerminal;
+  late String executable;
+  late List<String> processArgs;
+
+  factory MockDartCliDebugAdapter() {
+    final stdinController = StreamController<List<int>>();
+    final stdoutController = StreamController<List<int>>();
+    final channel = ByteStreamServerChannel(
+        stdinController.stream, stdoutController.sink, null);
+
+    return MockDartCliDebugAdapter._(
+        stdinController.sink, stdoutController.stream, channel);
+  }
+
+  MockDartCliDebugAdapter._(
+      this.stdin, this.stdout, ByteStreamServerChannel channel)
+      : super(channel);
+
+  Future<void> launchAsProcess(
+    String executable,
+    List<String> processArgs,
+  ) async {
+    this.launchedInTerminal = false;
+    this.executable = executable;
+    this.processArgs = processArgs;
+  }
+
+  Future<void> launchInEditorTerminal(
+    bool debug,
+    String terminalKind,
+    String executable,
+    List<String> processArgs,
+  ) async {
+    this.launchedInTerminal = true;
+    this.executable = executable;
+    this.processArgs = processArgs;
+  }
+}
+
+/// A [DartTestDebugAdapter] that captures what process/args will be launched.
+class MockDartTestDebugAdapter extends DartTestDebugAdapter {
+  final StreamSink<List<int>> stdin;
+  final Stream<List<int>> stdout;
+
+  late String executable;
+  late List<String> processArgs;
+
+  factory MockDartTestDebugAdapter() {
+    final stdinController = StreamController<List<int>>();
+    final stdoutController = StreamController<List<int>>();
+    final channel = ByteStreamServerChannel(
+        stdinController.stream, stdoutController.sink, null);
+
+    return MockDartTestDebugAdapter._(
+      stdinController.sink,
+      stdoutController.stream,
+      channel,
+    );
+  }
+
+  MockDartTestDebugAdapter._(
+      this.stdin, this.stdout, ByteStreamServerChannel channel)
+      : super(channel);
+
+  Future<void> launchAsProcess(
+    String executable,
+    List<String> processArgs,
+  ) async {
+    this.executable = executable;
+    this.processArgs = processArgs;
+  }
+}
+
+class MockRequest extends Request {
+  static var _requestId = 1;
+  MockRequest()
+      : super.fromMap({
+          'command': 'mock_command',
+          'type': 'mock_type',
+          'seq': _requestId++,
+        });
+}
diff --git a/pkg/dds/tool/dap/README.md b/pkg/dds/tool/dap/README.md
index ac86324..5dd4e54 100644
--- a/pkg/dds/tool/dap/README.md
+++ b/pkg/dds/tool/dap/README.md
@@ -38,6 +38,9 @@
 - `List<String>? toolArgs` - arguments for the Dart VM
 - `String? console` - if set to `"terminal"` or `"externalTerminal"` will be run using the `runInTerminal` reverse-request; otherwise the debug adapter spawns the Dart process
 - `bool? enableAsserts` - whether to enable asserts (if not supplied, defaults to enabled)
+- `String? customTool` - an optional tool to run instead of `dart` - the custom tool must be completely compatible with the tool/command it is replacing
+- `int? customToolReplacesArgs` - the number of arguments to delete from the beginning of the argument list when invoking `customTool` - e.g. setting `customTool` to `dart_test` and
+  `customToolReplacesArgs` to `2` for a test run would invoke `dart_test foo_test.dart` instead of `dart run test:test foo_test.dart` (if larger than the number of computed arguments all arguments will be removed, if not supplied will default to `0`)
 
 Arguments specific to `attachRequest` are:
 
diff --git a/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.1.expect b/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.1.expect
index 1c2210e..4e4f764 100644
--- a/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.1.expect
+++ b/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.1.expect
@@ -39,6 +39,7 @@
   ffi::unsized,
   ffi::sizeOf,
   ffi::Dart_NativeMessageHandler,
+  ffi::Abi,
   ffi::Allocator,
   ffi::AllocatorAlloc,
   ffi::Array,
@@ -112,6 +113,7 @@
   ffi::unsized,
   ffi::sizeOf,
   ffi::Dart_NativeMessageHandler,
+  ffi::Abi,
   ffi::Allocator,
   ffi::AllocatorAlloc,
   ffi::Array,
diff --git a/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.2.expect b/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.2.expect
index ae09732..ead3ad4 100644
--- a/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.2.expect
+++ b/pkg/front_end/testcases/incremental/no_outline_change_50_ffi.yaml.world.2.expect
@@ -39,6 +39,7 @@
   ffi::unsized,
   ffi::sizeOf,
   ffi::Dart_NativeMessageHandler,
+  ffi::Abi,
   ffi::Allocator,
   ffi::AllocatorAlloc,
   ffi::Array,
@@ -112,6 +113,7 @@
   ffi::unsized,
   ffi::sizeOf,
   ffi::Dart_NativeMessageHandler,
+  ffi::Abi,
   ffi::Allocator,
   ffi::AllocatorAlloc,
   ffi::Array,
diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.expect
index 0a88f78..0f9dc6d 100644
--- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.expect
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.expect
@@ -25,5 +25,5 @@
 
 Constructor coverage from constants:
 org-dartlang-testcase:///ffi_struct_inline_array.dart:
-- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9)
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9)
 - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.transformed.expect
index 0dc519f..edf8c40 100644
--- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.strong.transformed.expect
@@ -52,5 +52,5 @@
 
 Constructor coverage from constants:
 org-dartlang-testcase:///ffi_struct_inline_array.dart:
-- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9)
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9)
 - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.expect
index c47d067..732d282 100644
--- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.expect
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.expect
@@ -25,5 +25,5 @@
 
 Constructor coverage from constants:
 org-dartlang-testcase:///ffi_struct_inline_array.dart:
-- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9)
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9)
 - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.transformed.expect
index 6a48895..a445d62 100644
--- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.transformed.expect
@@ -52,5 +52,5 @@
 
 Constructor coverage from constants:
 org-dartlang-testcase:///ffi_struct_inline_array.dart:
-- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9)
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9)
 - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.expect
index 0dd2727..eaeea63 100644
--- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.expect
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.expect
@@ -33,5 +33,5 @@
 
 Constructor coverage from constants:
 org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:
-- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9)
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9)
 - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.transformed.expect
index 036c491..c77db0d 100644
--- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.transformed.expect
@@ -84,5 +84,5 @@
 
 Constructor coverage from constants:
 org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:
-- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9)
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9)
 - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.expect
index 8c3d96b..b6819cb 100644
--- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.expect
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.expect
@@ -33,5 +33,5 @@
 
 Constructor coverage from constants:
 org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:
-- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9)
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9)
 - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.transformed.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.transformed.expect
index 8d6be3c..7de8504 100644
--- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.transformed.expect
@@ -84,5 +84,5 @@
 
 Constructor coverage from constants:
 org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:
-- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:136:9)
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/ffi/ffi.dart:137:9)
 - Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/vm/lib/transformations/ffi/abi.dart b/pkg/vm/lib/transformations/ffi/abi.dart
index 795574a..04bc8e2 100644
--- a/pkg/vm/lib/transformations/ffi/abi.dart
+++ b/pkg/vm/lib/transformations/ffi/abi.dart
@@ -4,6 +4,8 @@
 
 import 'common.dart';
 
+// TODO(http://dartbug.com/47823): Remove this copy of `Abi`.
+
 /// The hardware architectures the Dart VM runs on.
 enum _Architecture {
   arm,
@@ -36,108 +38,209 @@
   windows,
 }
 
-/// Application binary interface.
+/// An application binary interface (ABI).
 ///
-/// The Dart VM can run on a variety of [Abi]s, see [supportedAbis].
+/// An ABI defines the memory layout of data
+/// and the function call protocol for native code.
+/// It is usually defined by the an operating system for each
+/// architecture that operating system runs on.
+///
+/// The Dart VM can run on a variety of operating systems and architectures.
+/// Supported ABIs are represented by `Abi` objects.
+/// See [values] for all the supported ABIs.
 class Abi {
+  /// The application binary interface for Android on the Arm architecture.
+  static const androidArm = _androidArm;
+
+  /// The application binary interface for Android on the Arm64 architecture.
+  static const androidArm64 = _androidArm64;
+
+  /// The application binary interface for Android on the IA32 architecture.
+  static const androidIA32 = _androidIA32;
+
+  /// The application binary interface for android on the X64 architecture.
+  static const androidX64 = _androidX64;
+
+  /// The application binary interface for Fuchsia on the Arm64 architecture.
+  static const fuchsiaArm64 = _fuchsiaArm64;
+
+  /// The application binary interface for Fuchsia on the X64 architecture.
+  static const fuchsiaX64 = _fuchsiaX64;
+
+  /// The application binary interface for iOS on the Arm architecture.
+  static const iosArm = _iosArm;
+
+  /// The application binary interface for iOS on the Arm64 architecture.
+  static const iosArm64 = _iosArm64;
+
+  /// The application binary interface for iOS on the X64 architecture.
+  static const iosX64 = _iosX64;
+
+  /// The application binary interface for Linux on the Arm architecture.
+  ///
+  /// Does not distinguish between hard and soft fp. Currently, no uses of Abi
+  /// require this distinction.
+  static const linuxArm = _linuxArm;
+
+  /// The application binary interface for linux on the Arm64 architecture.
+  static const linuxArm64 = _linuxArm64;
+
+  /// The application binary interface for linux on the IA32 architecture.
+  static const linuxIA32 = _linuxIA32;
+
+  /// The application binary interface for linux on the X64 architecture.
+  static const linuxX64 = _linuxX64;
+
+  /// The application binary interface for MacOS on the Arm64 architecture.
+  static const macosArm64 = _macosArm64;
+
+  /// The application binary interface for MacOS on the X64 architecture.
+  static const macosX64 = _macosX64;
+
+  /// The application binary interface for Windows on the Arm64 architecture.
+  static const windowsArm64 = _windowsArm64;
+
+  /// The application binary interface for Windows on the IA32 architecture.
+  static const windowsIA32 = _windowsIA32;
+
+  /// The application binary interface for Windows on the X64 architecture.
+  static const windowsX64 = _windowsX64;
+
+  /// The ABIs that the DartVM can run on, sorted alphabetically.
+  ///
+  /// Does not contain macosIA32, we stopped supporting it.
+  /// https://github.com/dart-lang/sdk/issues/39810
+  ///
+  /// Includes [windowsArm64], even though it is currently not supported.
+  /// Support has been requested for Flutter.
+  /// https://github.com/flutter/flutter/issues/53120
+  static const values = [
+    androidArm,
+    androidArm64,
+    androidIA32,
+    androidX64,
+    fuchsiaArm64,
+    fuchsiaX64,
+    iosArm,
+    iosArm64,
+    iosX64,
+    linuxArm,
+    linuxArm64,
+    linuxIA32,
+    linuxX64,
+    macosArm64,
+    macosX64,
+    windowsArm64,
+    windowsIA32,
+    windowsX64,
+  ];
+
+  /// The ABI the Dart VM is currently running on.
+  external factory Abi.current();
+
+  /// A string representation of this ABI.
+  ///
+  /// The string is equal to the 'on' part from `Platform.version` and
+  /// `dart --version`.
+  @override
+  String toString() => '${_os.name}_${_architecture.name}';
+
+  /// The size of both integer registers and memory addresses in bytes.
+  int get wordSize => _architecture.wordSize;
+
   /// The operating system of this [Abi].
-  // ignore: unused_field
   final _OS _os;
 
   /// The architecture of this [Abi].
   final _Architecture _architecture;
 
-  /// The size of integer registers and memory addresses in bytes.
-  int get wordSize => _architecture.wordSize;
-
+  /// The constructor is private so that we can use [Abi.values] as opaque
+  /// tokens.
   const Abi._(this._architecture, this._os);
+
+  static const _androidArm = Abi._(_Architecture.arm, _OS.android);
+  static const _androidArm64 = Abi._(_Architecture.arm64, _OS.android);
+  static const _androidIA32 = Abi._(_Architecture.ia32, _OS.android);
+  static const _androidX64 = Abi._(_Architecture.x64, _OS.android);
+  static const _fuchsiaArm64 = Abi._(_Architecture.arm64, _OS.fuchsia);
+  static const _fuchsiaX64 = Abi._(_Architecture.x64, _OS.fuchsia);
+  static const _iosArm = Abi._(_Architecture.arm, _OS.ios);
+  static const _iosArm64 = Abi._(_Architecture.arm64, _OS.ios);
+  static const _iosX64 = Abi._(_Architecture.x64, _OS.ios);
+  static const _linuxArm = Abi._(_Architecture.arm, _OS.linux);
+  static const _linuxArm64 = Abi._(_Architecture.arm64, _OS.linux);
+  static const _linuxIA32 = Abi._(_Architecture.ia32, _OS.linux);
+  static const _linuxX64 = Abi._(_Architecture.x64, _OS.linux);
+  static const _macosArm64 = Abi._(_Architecture.arm64, _OS.macos);
+  static const _macosX64 = Abi._(_Architecture.x64, _OS.macos);
+  static const _windowsArm64 = Abi._(_Architecture.arm64, _OS.windows);
+  static const _windowsIA32 = Abi._(_Architecture.ia32, _OS.windows);
+  static const _windowsX64 = Abi._(_Architecture.x64, _OS.windows);
 }
 
-const androidArm = Abi._(_Architecture.arm, _OS.android);
-const androidArm64 = Abi._(_Architecture.arm64, _OS.android);
-const androidIA32 = Abi._(_Architecture.ia32, _OS.android);
-const androidX64 = Abi._(_Architecture.x64, _OS.android);
-const fuchsiaArm64 = Abi._(_Architecture.arm64, _OS.fuchsia);
-const fuchsiaX64 = Abi._(_Architecture.x64, _OS.fuchsia);
-const iosArm = Abi._(_Architecture.arm, _OS.ios);
-const iosArm64 = Abi._(_Architecture.arm64, _OS.ios);
-const iosX64 = Abi._(_Architecture.x64, _OS.ios);
-const linuxArm = Abi._(_Architecture.arm, _OS.linux);
-const linuxArm64 = Abi._(_Architecture.arm64, _OS.linux);
-const linuxIA32 = Abi._(_Architecture.ia32, _OS.linux);
-const linuxX64 = Abi._(_Architecture.x64, _OS.linux);
-const macosArm64 = Abi._(_Architecture.arm64, _OS.macos);
-
-// No macosIA32, not intending to support.
-// https://github.com/dart-lang/sdk/issues/39810
-
-const macosX64 = Abi._(_Architecture.x64, _OS.macos);
-
-/// Currently not supported, but feature requested for Flutter.
-/// https://github.com/flutter/flutter/issues/53120
-const windowsArm64 = Abi._(_Architecture.arm64, _OS.windows);
-const windowsIA32 = Abi._(_Architecture.ia32, _OS.windows);
-const windowsX64 = Abi._(_Architecture.x64, _OS.windows);
-
-/// All ABIs that the DartVM can run on sorted alphabetically.
-///
-/// Keep consistent with runtime/vm/compiler/ffi/abi.cc.
-const supportedAbisOrdered = [
-  androidArm,
-  androidArm64,
-  androidIA32,
-  androidX64,
-  fuchsiaArm64,
-  fuchsiaX64,
-  iosArm,
-  iosArm64,
-  iosX64,
-  linuxArm,
-  linuxArm64,
-  linuxIA32,
-  linuxX64,
-  macosArm64,
-  macosX64,
-  windowsArm64,
-  windowsIA32,
-  windowsX64,
-];
+// Keep consistent with sdk/lib/ffi/abi.dart.
+const Map<Abi, String> abiNames = {
+  Abi.androidArm: 'androidArm',
+  Abi.androidArm64: 'androidArm64',
+  Abi.androidIA32: 'androidIA32',
+  Abi.androidX64: 'androidX64',
+  Abi.fuchsiaArm64: 'fuchsiaArm64',
+  Abi.fuchsiaX64: 'fuchsiaX64',
+  Abi.iosArm: 'iosArm',
+  Abi.iosArm64: 'iosArm64',
+  Abi.iosX64: 'iosX64',
+  Abi.linuxArm: 'linuxArm',
+  Abi.linuxArm64: 'linuxArm64',
+  Abi.linuxIA32: 'linuxIA32',
+  Abi.linuxX64: 'linuxX64',
+  Abi.macosArm64: 'macosArm64',
+  Abi.macosX64: 'macosX64',
+  Abi.windowsArm64: 'windowsArm64',
+  Abi.windowsIA32: 'windowsIA32',
+  Abi.windowsX64: 'windowsX64',
+};
 
 /// The size of integer registers and memory addresses in bytes per [Abi].
 // Keep consistent with sdk/lib/_internal/vm/lib/ffi_patch.dart
-final Map<Abi, int> wordSize = {
-  for (final abi in supportedAbisOrdered) abi: abi.wordSize
-};
+final Map<Abi, int> wordSize =
+    Map.unmodifiable({for (final abi in Abi.values) abi: abi.wordSize});
 
-/// Struct and union fields that are not aligned to their size.
+/// Alignment for types that are not aligned to a multiple of their size.
 ///
-/// Has an entry for all Abis. Empty entries document that every native
-/// type is aligned to it's own size in this ABI.
+/// When a type occurs in a struct or union, it's usually aligned
+/// to a multiple of its own size.
+/// Some ABIs have types which are not aligned to their own size,
+/// but to a smaller size.
+///
+/// This map maps each [Abi] to a mapping from types that are not
+/// aligned by their size, to their actual alignment.
+/// If such a map is empty, which many are,
+/// it means that all types are aligned to their own size in that ABI.
 ///
 /// See runtime/vm/compiler/ffi/abi.cc for asserts in the VM that verify these
 /// alignments.
-const nonSizeAlignment = <Abi, Map<NativeType, int>>{
+const Map<Abi, Map<NativeType, int>> nonSizeAlignment = {
   // _wordSize64
-  androidArm64: _wordSize64,
-  androidX64: _wordSize64,
-  fuchsiaArm64: _wordSize64,
-  fuchsiaX64: _wordSize64,
-  iosArm64: _wordSize64,
-  iosX64: _wordSize64,
-  linuxArm64: _wordSize64,
-  linuxX64: _wordSize64,
-  macosArm64: _wordSize64,
-  macosX64: _wordSize64,
-  windowsArm64: _wordSize64,
-  windowsX64: _wordSize64,
+  Abi.androidArm64: _wordSize64,
+  Abi.androidX64: _wordSize64,
+  Abi.fuchsiaArm64: _wordSize64,
+  Abi.fuchsiaX64: _wordSize64,
+  Abi.iosArm64: _wordSize64,
+  Abi.iosX64: _wordSize64,
+  Abi.linuxArm64: _wordSize64,
+  Abi.linuxX64: _wordSize64,
+  Abi.macosArm64: _wordSize64,
+  Abi.macosX64: _wordSize64,
+  Abi.windowsArm64: _wordSize64,
+  Abi.windowsX64: _wordSize64,
   // _wordSize32Align32
-  androidIA32: _wordSize32Align32,
-  iosArm: _wordSize32Align32,
-  linuxIA32: _wordSize32Align32,
+  Abi.androidIA32: _wordSize32Align32,
+  Abi.iosArm: _wordSize32Align32,
+  Abi.linuxIA32: _wordSize32Align32,
   // _wordSize32Align64
-  androidArm: _wordSize32Align64,
-  linuxArm: _wordSize32Align64,
-  windowsIA32: _wordSize32Align64,
+  Abi.androidArm: _wordSize32Align64,
+  Abi.linuxArm: _wordSize32Align64,
+  Abi.windowsIA32: _wordSize32Align64,
 };
 
 // All 64 bit ABIs align struct fields to their size.
@@ -170,6 +273,6 @@
 // > compatible with structures in code compiled without that switch.
 // https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html
 //
-// ARM always requires 8 byte alignment for 8 byte values:
+// Arm always requires 8 byte alignment for 8 byte values:
 // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042d/IHI0042D_aapcs.pdf 4.1 Fundamental Data Types
 const Map<NativeType, int> _wordSize32Align64 = {};
diff --git a/pkg/vm/lib/transformations/ffi/common.dart b/pkg/vm/lib/transformations/ffi/common.dart
index 7627a59..54ffd5c 100644
--- a/pkg/vm/lib/transformations/ffi/common.dart
+++ b/pkg/vm/lib/transformations/ffi/common.dart
@@ -547,7 +547,7 @@
     return InstanceInvocation(
         InstanceAccessKind.Instance,
         intListConstantExpression([
-          for (final abi in supportedAbisOrdered) values[abi]!,
+          for (final abi in Abi.values) values[abi]!,
         ]),
         listElementAt.name,
         Arguments([StaticInvocation(abiMethod, Arguments([]))]),
diff --git a/pkg/vm/lib/transformations/ffi/definitions.dart b/pkg/vm/lib/transformations/ffi/definitions.dart
index c11b710..29ae1d9 100644
--- a/pkg/vm/lib/transformations/ffi/definitions.dart
+++ b/pkg/vm/lib/transformations/ffi/definitions.dart
@@ -849,8 +849,7 @@
   void _addSizeOfField(Class compound, IndexedClass? indexedClass,
       [Map<Abi, int>? sizes = null]) {
     if (sizes == null) {
-      sizes =
-          Map.fromEntries(supportedAbisOrdered.map((abi) => MapEntry(abi, 0)));
+      sizes = {for (var abi in Abi.values) abi: 0};
     }
     final name = Name("#sizeOf");
     final getterReference = indexedClass?.lookupGetterReference(name);
@@ -1096,14 +1095,14 @@
     if (size == WORD_SIZE) {
       return wordSize;
     }
-    return Map.fromEntries(
-        supportedAbisOrdered.map((abi) => MapEntry(abi, size)));
+    return {for (var abi in Abi.values) abi: size};
   }
 
   @override
-  Map<Abi, int> get alignment =>
-      Map.fromEntries(supportedAbisOrdered.map((abi) =>
-          MapEntry(abi, nonSizeAlignment[abi]![nativeType] ?? size[abi]!)));
+  Map<Abi, int> get alignment => {
+        for (var abi in Abi.values)
+          abi: nonSizeAlignment[abi]![nativeType] ?? size[abi]!
+      };
 
   @override
   Constant generateConstant(FfiTransformer transformer) =>
@@ -1324,8 +1323,9 @@
 
   factory StructNativeTypeCfe(Class clazz, List<NativeTypeCfe> members,
       {int? packing}) {
-    final layout = Map.fromEntries(supportedAbisOrdered
-        .map((abi) => MapEntry(abi, _calculateLayout(members, packing, abi))));
+    final layout = {
+      for (var abi in Abi.values) abi: _calculateLayout(members, packing, abi)
+    };
     return StructNativeTypeCfe._(clazz, members, packing, layout);
   }
 
@@ -1360,8 +1360,9 @@
 
 class UnionNativeTypeCfe extends CompoundNativeTypeCfe {
   factory UnionNativeTypeCfe(Class clazz, List<NativeTypeCfe> members) {
-    final layout = Map.fromEntries(supportedAbisOrdered
-        .map((abi) => MapEntry(abi, _calculateLayout(members, abi))));
+    final layout = {
+      for (var abi in Abi.values) abi: _calculateLayout(members, abi)
+    };
     return UnionNativeTypeCfe._(clazz, members, layout);
   }
 
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/enum_from_lib_used_as_type.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/enum_from_lib_used_as_type.dart.expect
index 463adf9..db77666 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/enum_from_lib_used_as_type.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/enum_from_lib_used_as_type.dart.expect
@@ -22,6 +22,6 @@
   synthetic constructor •() → self::Class
     : super core::Object::•()
     ;
-[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3252,getterSelectorId:3253]  method method([@vm.inferred-type.metadata=dart.core::Null? (value: null)] self::Enum e) → core::int
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3254,getterSelectorId:3255]  method method([@vm.inferred-type.metadata=dart.core::Null? (value: null)] self::Enum e) → core::int
     return [@vm.inferred-type.metadata=!] e.{core::_Enum::index}{core::int};
 }
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/tree_shake_enum_from_lib.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/tree_shake_enum_from_lib.dart.expect
index 3a69351..3aa20dc 100644
--- a/pkg/vm/testcases/transformations/type_flow/transformer/tree_shake_enum_from_lib.dart.expect
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/tree_shake_enum_from_lib.dart.expect
@@ -51,6 +51,6 @@
   synthetic constructor •() → self::ConstClass
     : super core::Object::•()
     ;
-[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3256,getterSelectorId:3257]  method method([@vm.inferred-type.metadata=dart.core::Null? (value: null)] self::ConstEnum e) → core::int
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3258,getterSelectorId:3259]  method method([@vm.inferred-type.metadata=dart.core::Null? (value: null)] self::ConstEnum e) → core::int
     return [@vm.inferred-type.metadata=!] e.{core::_Enum::index}{core::int};
 }
diff --git a/sdk/lib/_internal/vm/lib/ffi_patch.dart b/sdk/lib/_internal/vm/lib/ffi_patch.dart
index 8816c44..14180b6 100644
--- a/sdk/lib/_internal/vm/lib/ffi_patch.dart
+++ b/sdk/lib/_internal/vm/lib/ffi_patch.dart
@@ -228,6 +228,14 @@
     "Recognized method: IR graph is built in the flow graph builder.")
 external int _abi();
 
+@patch
+@pragma("vm:entry-point")
+class Abi {
+  @patch
+  @pragma("vm:prefer-inline")
+  factory Abi.current() => values[_abi()];
+}
+
 /// Copies data byte-wise from [source] to [target].
 ///
 /// [source] and [target] should either be [Pointer] or [TypedData].
@@ -943,14 +951,14 @@
   @patch
   T operator [](int index) {
     throw ArgumentError(
-        "S ($T) should be a subtype of Struct at compile-time.");
+        "T ($T) should be a subtype of Struct at compile-time.");
   }
 }
 
 extension UnionArray<T extends Union> on Array<T> {
   @patch
   T operator [](int index) {
-    throw ArgumentError("S ($T) should be a subtype of Union at compile-time.");
+    throw ArgumentError("T ($T) should be a subtype of Union at compile-time.");
   }
 }
 
diff --git a/sdk/lib/ffi/abi.dart b/sdk/lib/ffi/abi.dart
new file mode 100644
index 0000000..f5cba04
--- /dev/null
+++ b/sdk/lib/ffi/abi.dart
@@ -0,0 +1,160 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of dart.ffi;
+
+/// An application binary interface (ABI).
+///
+/// An ABI defines the memory layout of data
+/// and the function call protocol for native code.
+/// It is usually defined by the an operating system for each
+/// architecture that operating system runs on.
+///
+/// The Dart VM can run on a variety of operating systems and architectures.
+/// Supported ABIs are represented by `Abi` objects.
+/// See [values] for all the supported ABIs.
+class Abi {
+  /// The application binary interface for Android on the Arm architecture.
+  static const androidArm = _androidArm;
+
+  /// The application binary interface for Android on the Arm64 architecture.
+  static const androidArm64 = _androidArm64;
+
+  /// The application binary interface for Android on the IA32 architecture.
+  static const androidIA32 = _androidIA32;
+
+  /// The application binary interface for android on the X64 architecture.
+  static const androidX64 = _androidX64;
+
+  /// The application binary interface for Fuchsia on the Arm64 architecture.
+  static const fuchsiaArm64 = _fuchsiaArm64;
+
+  /// The application binary interface for Fuchsia on the X64 architecture.
+  static const fuchsiaX64 = _fuchsiaX64;
+
+  /// The application binary interface for iOS on the Arm architecture.
+  static const iosArm = _iosArm;
+
+  /// The application binary interface for iOS on the Arm64 architecture.
+  static const iosArm64 = _iosArm64;
+
+  /// The application binary interface for iOS on the X64 architecture.
+  static const iosX64 = _iosX64;
+
+  /// The application binary interface for Linux on the Arm architecture.
+  ///
+  /// Does not distinguish between hard and soft fp. Currently, no uses of Abi
+  /// require this distinction.
+  static const linuxArm = _linuxArm;
+
+  /// The application binary interface for linux on the Arm64 architecture.
+  static const linuxArm64 = _linuxArm64;
+
+  /// The application binary interface for linux on the IA32 architecture.
+  static const linuxIA32 = _linuxIA32;
+
+  /// The application binary interface for linux on the X64 architecture.
+  static const linuxX64 = _linuxX64;
+
+  /// The application binary interface for MacOS on the Arm64 architecture.
+  static const macosArm64 = _macosArm64;
+
+  /// The application binary interface for MacOS on the X64 architecture.
+  static const macosX64 = _macosX64;
+
+  /// The application binary interface for Windows on the Arm64 architecture.
+  static const windowsArm64 = _windowsArm64;
+
+  /// The application binary interface for Windows on the IA32 architecture.
+  static const windowsIA32 = _windowsIA32;
+
+  /// The application binary interface for Windows on the X64 architecture.
+  static const windowsX64 = _windowsX64;
+
+  /// The ABIs that the DartVM can run on.
+  ///
+  /// Does not contain a `macosIA32`. We have stopped supporting 32-bit MacOS.
+  ///
+  /// Includes [windowsArm64], even though it is currently not supported.
+  /// Support has been requested for Flutter.
+  /// https://github.com/flutter/flutter/issues/53120
+  // TODO(http://dartbug.com/47824): Remove the above comment when supported.
+  static const values = [
+    androidArm,
+    androidArm64,
+    androidIA32,
+    androidX64,
+    fuchsiaArm64,
+    fuchsiaX64,
+    iosArm,
+    iosArm64,
+    iosX64,
+    linuxArm,
+    linuxArm64,
+    linuxIA32,
+    linuxX64,
+    macosArm64,
+    macosX64,
+    windowsArm64,
+    windowsIA32,
+    windowsX64,
+  ];
+
+  /// The ABI the Dart VM is currently running on.
+  external factory Abi.current();
+
+  /// A string representation of this ABI.
+  ///
+  /// The string is equal to the 'on' part from `Platform.version` and
+  /// `dart --version`.
+  @override
+  String toString() => '${_os.name}_${_architecture.name}';
+
+  /// The operating system of this [Abi].
+  final _OS _os;
+
+  /// The architecture of this [Abi].
+  final _Architecture _architecture;
+
+  /// The constructor is private so that we can use [Abi.values] as opaque
+  /// tokens.
+  const Abi._(this._architecture, this._os);
+
+  static const _androidArm = Abi._(_Architecture.arm, _OS.android);
+  static const _androidArm64 = Abi._(_Architecture.arm64, _OS.android);
+  static const _androidIA32 = Abi._(_Architecture.ia32, _OS.android);
+  static const _androidX64 = Abi._(_Architecture.x64, _OS.android);
+  static const _fuchsiaArm64 = Abi._(_Architecture.arm64, _OS.fuchsia);
+  static const _fuchsiaX64 = Abi._(_Architecture.x64, _OS.fuchsia);
+  static const _iosArm = Abi._(_Architecture.arm, _OS.ios);
+  static const _iosArm64 = Abi._(_Architecture.arm64, _OS.ios);
+  static const _iosX64 = Abi._(_Architecture.x64, _OS.ios);
+  static const _linuxArm = Abi._(_Architecture.arm, _OS.linux);
+  static const _linuxArm64 = Abi._(_Architecture.arm64, _OS.linux);
+  static const _linuxIA32 = Abi._(_Architecture.ia32, _OS.linux);
+  static const _linuxX64 = Abi._(_Architecture.x64, _OS.linux);
+  static const _macosArm64 = Abi._(_Architecture.arm64, _OS.macos);
+  static const _macosX64 = Abi._(_Architecture.x64, _OS.macos);
+  static const _windowsArm64 = Abi._(_Architecture.arm64, _OS.windows);
+  static const _windowsIA32 = Abi._(_Architecture.ia32, _OS.windows);
+  static const _windowsX64 = Abi._(_Architecture.x64, _OS.windows);
+}
+
+/// The hardware architectures the Dart VM runs on.
+enum _Architecture {
+  arm,
+  arm64,
+  ia32,
+  x64,
+}
+
+/// The operating systems the Dart VM runs on.
+enum _OS {
+  android,
+  fuchsia,
+  ios,
+  linux,
+  macos,
+  windows,
+}
diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart
index fa98c88..d3d19c2 100644
--- a/sdk/lib/ffi/ffi.dart
+++ b/sdk/lib/ffi/ffi.dart
@@ -15,12 +15,13 @@
 import 'dart:isolate';
 import 'dart:typed_data';
 
-part "native_type.dart";
-part "allocation.dart";
-part "annotations.dart";
-part "dynamic_library.dart";
-part "struct.dart";
-part "union.dart";
+part 'abi.dart';
+part 'native_type.dart';
+part 'allocation.dart';
+part 'annotations.dart';
+part 'dynamic_library.dart';
+part 'struct.dart';
+part 'union.dart';
 
 /// Number of bytes used by native type T.
 ///
@@ -29,12 +30,12 @@
 /// This function must be invoked with a compile-time constant [T].
 external int sizeOf<T extends NativeType>();
 
-/// Represents a pointer into the native C memory corresponding to "NULL", e.g.
+/// Represents a pointer into the native C memory corresponding to 'NULL', e.g.
 /// a pointer with address 0.
 final Pointer<Never> nullptr = Pointer.fromAddress(0);
 
 /// Represents a pointer into the native C memory. Cannot be extended.
-@pragma("vm:entry-point")
+@pragma('vm:entry-point')
 class Pointer<T extends NativeType> extends NativeType {
   /// Construction from raw integer.
   external factory Pointer.fromAddress(int ptr);
@@ -56,7 +57,7 @@
   /// Does not accept dynamic invocations -- where the type of the receiver is
   /// [dynamic].
   external static Pointer<NativeFunction<T>> fromFunction<T extends Function>(
-      @DartRepresentationOf("T") Function f,
+      @DartRepresentationOf('T') Function f,
       [Object? exceptionalReturn]);
 
   /// Access to the raw pointer value.
@@ -150,7 +151,7 @@
     on Pointer<NativeFunction<NF>> {
   /// Convert to Dart function, automatically marshalling the arguments
   /// and return value.
-  external DF asFunction<@DartRepresentationOf("NF") DF extends Function>(
+  external DF asFunction<@DartRepresentationOf('NF') DF extends Function>(
       {bool isLeaf: false});
 }
 
@@ -827,7 +828,7 @@
 ///
 /// Example:
 ///```dart template:none
-/// @FfiNative<Int64 Function(Int64, Int64)>("FfiNative_Sum", isLeaf:true)
+/// @FfiNative<Int64 Function(Int64, Int64)>('FfiNative_Sum', isLeaf:true)
 /// external int sum(int a, int b);
 ///```
 /// Calling such functions will throw an exception if no resolver
@@ -844,7 +845,7 @@
 
 // Bootstrapping native for getting the FFI native C function pointer to look
 // up the FFI resolver.
-@pragma("vm:external-name", "Ffi_GetFfiNativeResolver")
+@pragma('vm:external-name', 'Ffi_GetFfiNativeResolver')
 external Pointer<NativeFunction<IntPtr Function(Handle, Handle, IntPtr)>>
     _get_ffi_native_resolver<T extends NativeFunction>();
 
diff --git a/sdk/lib/ffi/ffi_sources.gni b/sdk/lib/ffi/ffi_sources.gni
index 4152f91..f7b2838 100644
--- a/sdk/lib/ffi/ffi_sources.gni
+++ b/sdk/lib/ffi/ffi_sources.gni
@@ -6,6 +6,7 @@
   "ffi.dart",
 
   # The above file needs to be first as it lists the parts below.
+  "abi.dart",
   "allocation.dart",
   "annotations.dart",
   "dynamic_library.dart",
diff --git a/tests/ffi/abi_test.dart b/tests/ffi/abi_test.dart
new file mode 100644
index 0000000..15c6776
--- /dev/null
+++ b/tests/ffi/abi_test.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:expect/expect.dart';
+
+void main() {
+  testCurrent();
+  testPlatformVersionCompatibility();
+}
+
+void testCurrent() {
+  final currentAbi = Abi.current();
+  Expect.isTrue(Abi.values.contains(currentAbi));
+}
+
+void testPlatformVersionCompatibility() {
+  final abiStringFromPlatformVersion = Platform.version.split('"')[1];
+  final abiStringFromCurrent = Abi.current().toString();
+  Expect.equals(abiStringFromPlatformVersion, abiStringFromCurrent);
+}
diff --git a/tests/ffi_2/abi_test.dart b/tests/ffi_2/abi_test.dart
new file mode 100644
index 0000000..d9c152e
--- /dev/null
+++ b/tests/ffi_2/abi_test.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart = 2.9
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:expect/expect.dart';
+
+void main() {
+  testCurrent();
+  testPlatformVersionCompatibility();
+}
+
+void testCurrent() {
+  final currentAbi = Abi.current();
+  Expect.isTrue(Abi.values.contains(currentAbi));
+}
+
+void testPlatformVersionCompatibility() {
+  final abiStringFromPlatformVersion = Platform.version.split('"')[1];
+  final abiStringFromCurrent = Abi.current().toString();
+  Expect.equals(abiStringFromPlatformVersion, abiStringFromCurrent);
+}
diff --git a/tools/VERSION b/tools/VERSION
index 3f42e93..4d4dd33 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 16
 PATCH 0
-PRERELEASE 68
+PRERELEASE 69
 PRERELEASE_PATCH 0
\ No newline at end of file