Version 2.13.0-121.0.dev

Merge commit 'bcf06bad4a1d925cc329bb94f1fbaff358a9207b' into 'dev'
diff --git a/DEPS b/DEPS
index f3aec31..8c55dd0 100644
--- a/DEPS
+++ b/DEPS
@@ -39,7 +39,7 @@
 
   # Checked-in SDK version. The checked-in SDK is a Dart SDK distribution in a
   # cipd package used to run Dart scripts in the build and test infrastructure.
-  "sdk_tag": "version:2.12.0-259.8.beta",
+  "sdk_tag": "version:2.12.0",
 
   # co19 is a cipd package. Use update.sh in tests/co19[_2] to update these
   # hashes. It requires access to the dart-build-access group, which EngProd
diff --git a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
index 766b57a..fe7cfc1 100644
--- a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -4065,7 +4065,7 @@
       Variable? exceptionVariable, Variable? stackTraceVariable) {
     _TryContext<Variable, Type> context =
         _stack.last as _TryContext<Variable, Type>;
-    _current = context._beforeCatch;
+    _current = context._beforeCatch!;
     if (exceptionVariable != null) {
       _current = _current.declare(exceptionVariable, true);
     }
@@ -4086,7 +4086,7 @@
   void tryCatchStatement_end() {
     _TryContext<Variable, Type> context =
         _stack.removeLast() as _TryContext<Variable, Type>;
-    _current = context._afterBodyAndCatches.unsplit();
+    _current = context._afterBodyAndCatches!.unsplit();
   }
 
   @override
@@ -4101,7 +4101,7 @@
         'No assigned variables info should have been stored for $finallyBlock');
     _TryFinallyContext<Variable, Type> context =
         _stack.removeLast() as _TryFinallyContext<Variable, Type>;
-    _current = context._afterBodyAndCatches
+    _current = context._afterBodyAndCatches!
         .attachFinally(typeOperations, context._beforeFinally, _current);
   }
 
@@ -5051,14 +5051,14 @@
     extends _SimpleContext<Variable, Type> {
   /// If the statement is a "try/catch" statement, the flow model representing
   /// program state at the top of any `catch` block.
-  late final FlowModel<Variable, Type> _beforeCatch;
+  FlowModel<Variable, Type>? _beforeCatch;
 
   /// If the statement is a "try/catch" statement, the accumulated flow model
   /// representing program state after the `try` block or one of the `catch`
   /// blocks has finished executing.  If the statement is a "try/finally"
   /// statement, the flow model representing program state after the `try` block
   /// has finished executing.
-  late FlowModel<Variable, Type> _afterBodyAndCatches;
+  FlowModel<Variable, Type>? _afterBodyAndCatches;
 
   _TryContext(FlowModel<Variable, Type> previous) : super(previous);
 
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index d77d6dc..f57c05b 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -3926,6 +3926,32 @@
 }
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<
+    Message Function(
+        String
+            name)> templateFfiSizeAnnotationDimensions = const Template<
+        Message Function(String name)>(
+    messageTemplate:
+        r"""Field '#name' must have an 'Array' annotation that matches the dimensions.""",
+    withArguments: _withArgumentsFfiSizeAnnotationDimensions);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(String name)> codeFfiSizeAnnotationDimensions =
+    const Code<Message Function(String name)>(
+  "FfiSizeAnnotationDimensions",
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsFfiSizeAnnotationDimensions(String name) {
+  if (name.isEmpty) throw 'No name provided';
+  name = demangleMixinApplicationName(name);
+  return new Message(codeFfiSizeAnnotationDimensions,
+      message:
+          """Field '${name}' must have an 'Array' annotation that matches the dimensions.""",
+      arguments: {'name': name});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<Message Function(String name)> templateFfiStructGeneric =
     const Template<Message Function(String name)>(
         messageTemplate: r"""Struct '#name' should not be generic.""",
diff --git a/pkg/analyzer/lib/error/error.dart b/pkg/analyzer/lib/error/error.dart
index 04edcc1..fd9f767 100644
--- a/pkg/analyzer/lib/error/error.dart
+++ b/pkg/analyzer/lib/error/error.dart
@@ -476,6 +476,7 @@
   FfiCode.NON_CONSTANT_TYPE_ARGUMENT,
   FfiCode.NON_NATIVE_FUNCTION_TYPE_ARGUMENT_TO_POINTER,
   FfiCode.NON_SIZED_TYPE_ARGUMENT,
+  FfiCode.SIZE_ANNOTATION_DIMENSIONS,
   FfiCode.SUBTYPE_OF_FFI_CLASS_IN_EXTENDS,
   FfiCode.SUBTYPE_OF_FFI_CLASS_IN_IMPLEMENTS,
   FfiCode.SUBTYPE_OF_FFI_CLASS_IN_WITH,
diff --git a/pkg/analyzer/lib/src/dart/error/ffi_code.dart b/pkg/analyzer/lib/src/dart/error/ffi_code.dart
index 83f1a49..08f603f 100644
--- a/pkg/analyzer/lib/src/dart/error/ffi_code.dart
+++ b/pkg/analyzer/lib/src/dart/error/ffi_code.dart
@@ -207,6 +207,15 @@
           "or subtype of 'Struct'.");
 
   /**
+   * No parameters.
+   */
+  static const FfiCode SIZE_ANNOTATION_DIMENSIONS = FfiCode(
+      name: 'SIZE_ANNOTATION_DIMENSIONS',
+      message:
+          "'Array's must have an 'Array' annotation that matches the dimensions.",
+      correction: "Try adjusting the arguments in the 'Array' annotation.");
+
+  /**
    * Parameters:
    * 0: the name of the subclass
    * 1: the name of the class being extended, implemented, or mixed in
diff --git a/pkg/analyzer/lib/src/generated/ffi_verifier.dart b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
index 7ab1196..99f293a 100644
--- a/pkg/analyzer/lib/src/generated/ffi_verifier.dart
+++ b/pkg/analyzer/lib/src/generated/ffi_verifier.dart
@@ -18,7 +18,7 @@
   static const _allocatorClassName = 'Allocator';
   static const _allocateExtensionMethodName = 'call';
   static const _allocatorExtensionName = 'AllocatorAlloc';
-  static const _cArrayClassName = 'Array';
+  static const _arrayClassName = 'Array';
   static const _dartFfiLibraryName = 'dart.ffi';
   static const _opaqueClassName = 'Opaque';
 
@@ -248,6 +248,9 @@
     if (nativeType.isPointer) {
       return true;
     }
+    if (nativeType.isArray) {
+      return true;
+    }
     return false;
   }
 
@@ -554,9 +557,10 @@
         final typeArg = (declaredType as InterfaceType).typeArguments.single;
         if (!_isSized(typeArg)) {
           _errorReporter.reportErrorForNode(FfiCode.NON_SIZED_TYPE_ARGUMENT,
-              fieldType, [_cArrayClassName, typeArg.toString()]);
+              fieldType, [_arrayClassName, typeArg.toString()]);
         }
-        _validateSizeOfAnnotation(fieldType, annotations);
+        final arrayDimensions = declaredType.arrayDimensions;
+        _validateSizeOfAnnotation(fieldType, annotations, arrayDimensions);
       } else if (declaredType.isStructSubtype) {
         final clazz = (declaredType as InterfaceType).element;
         if (clazz.isEmptyStruct) {
@@ -708,8 +712,8 @@
   /// Validate that the [annotations] include exactly one size annotation. If
   /// an error is produced that cannot be associated with an annotation,
   /// associate it with the [errorNode].
-  void _validateSizeOfAnnotation(
-      AstNode errorNode, NodeList<Annotation> annotations) {
+  void _validateSizeOfAnnotation(AstNode errorNode,
+      NodeList<Annotation> annotations, int arrayDimensions) {
     final ffiSizeAnnotations = annotations.where((annotation) {
       final element = annotation.element;
       return element is ConstructorElement &&
@@ -720,7 +724,9 @@
     if (ffiSizeAnnotations.isEmpty) {
       _errorReporter.reportErrorForNode(
           FfiCode.MISSING_SIZE_ANNOTATION_CARRAY, errorNode);
+      return;
     }
+
     if (ffiSizeAnnotations.length > 1) {
       final extraAnnotations = ffiSizeAnnotations.skip(1);
       for (final annotation in extraAnnotations) {
@@ -728,6 +734,24 @@
             FfiCode.EXTRA_SIZE_ANNOTATION_CARRAY, annotation);
       }
     }
+
+    // Check number of dimensions.
+    final annotation = ffiSizeAnnotations.first;
+    final expressions = annotation.arguments!.arguments;
+    int annotationDimensions = 0;
+    for (var expression in expressions) {
+      if (expression is IntegerLiteral) {
+        // Element of `@Array(1, 2, 3)`.
+        annotationDimensions++;
+      } else if (expression is ListLiteral) {
+        // Element of `@Array.multi([1, 2, 3])`.
+        annotationDimensions += expression.elements.length;
+      }
+    }
+    if (annotationDimensions != arrayDimensions) {
+      _errorReporter.reportErrorForNode(
+          FfiCode.SIZE_ANNOTATION_DIMENSIONS, annotation);
+    }
   }
 
   /// Validate that the given [typeArgument] has a constant value. Return `true`
@@ -845,11 +869,23 @@
     final self = this;
     if (self is InterfaceType) {
       final element = self.element;
-      return element.name == FfiVerifier._cArrayClassName && element.isFfiClass;
+      return element.name == FfiVerifier._arrayClassName && element.isFfiClass;
     }
     return false;
   }
 
+  int get arrayDimensions {
+    DartType iterator = this;
+    int dimensions = 0;
+    while (iterator is InterfaceType &&
+        iterator.element.name == FfiVerifier._arrayClassName &&
+        iterator.element.isFfiClass) {
+      dimensions++;
+      iterator = iterator.typeArguments.single;
+    }
+    return dimensions;
+  }
+
   bool get isPointer {
     final self = this;
     return self is InterfaceType && self.element.isPointer;
diff --git a/pkg/analyzer/lib/src/services/available_declarations.dart b/pkg/analyzer/lib/src/services/available_declarations.dart
index 07c970a..577424b 100644
--- a/pkg/analyzer/lib/src/services/available_declarations.dart
+++ b/pkg/analyzer/lib/src/services/available_declarations.dart
@@ -596,7 +596,7 @@
 
     if (_scheduledFiles.isNotEmpty) {
       var scheduledFile = _scheduledFiles.removeLast();
-      var file = _getFileByPath(scheduledFile.context, scheduledFile.path)!;
+      var file = _getFileByPath(scheduledFile.context, [], scheduledFile.path)!;
 
       if (!file.isLibrary) return;
 
@@ -653,6 +653,16 @@
     }
   }
 
+  /// TODO(scheglov) Remove after fixing
+  /// https://github.com/dart-lang/sdk/issues/45233
+  void _addPathOrUri(List<String> pathOrUriList, String path, Uri uri) {
+    pathOrUriList.add('(uri: $uri, path: $path)');
+
+    if (pathOrUriList.length > 200) {
+      throw StateError('Suspected cycle. $pathOrUriList');
+    }
+  }
+
   /// Compute exported declarations for the given [libraries].
   void _computeExportedDeclarations(Set<_File> libraries) {
     var walker = _LibraryWalker();
@@ -686,7 +696,8 @@
     return null;
   }
 
-  _File? _getFileByPath(DeclarationsContext context, String path) {
+  _File? _getFileByPath(
+      DeclarationsContext context, List<String> partOrUriList, String path) {
     var file = _pathToFile[path];
     if (file == null) {
       var uri = context._restoreUri(path);
@@ -694,13 +705,16 @@
         file = _File(this, path, uri);
         _pathToFile[path] = file;
         _uriToFile[uri] = file;
-        file.refresh(context);
+        _addPathOrUri(partOrUriList, path, uri);
+        file.refresh(context, partOrUriList);
+        partOrUriList.removeLast();
       }
     }
     return file;
   }
 
-  _File? _getFileByUri(DeclarationsContext context, Uri uri) {
+  _File? _getFileByUri(
+      DeclarationsContext context, List<String> partOrUriList, Uri uri) {
     var file = _uriToFile[uri];
     if (file != null) {
       return file;
@@ -726,7 +740,9 @@
     _pathToFile[path] = file;
     _uriToFile[uri] = file;
 
-    file.refresh(context);
+    _addPathOrUri(partOrUriList, path, uri);
+    file.refresh(context, partOrUriList);
+    partOrUriList.removeLast();
     return file;
   }
 
@@ -745,13 +761,13 @@
     var containingContext = _findContextOfPath(path);
     if (containingContext == null) return;
 
-    var file = _getFileByPath(containingContext, path);
+    var file = _getFileByPath(containingContext, [], path);
     if (file == null) return;
 
     var wasLibrary = file.isLibrary;
     var oldLibrary = wasLibrary ? file : file.library;
 
-    file.refresh(containingContext);
+    file.refresh(containingContext, []);
     var isLibrary = file.isLibrary;
     var newLibrary = isLibrary ? file : file.library;
 
@@ -763,17 +779,17 @@
       } else {
         notLibraries.add(file);
         if (newLibrary != null) {
-          newLibrary.refresh(containingContext);
+          newLibrary.refresh(containingContext, []);
           _invalidateExportedDeclarations(invalidatedLibraries, newLibrary);
         }
       }
     } else {
       if (oldLibrary != null) {
-        oldLibrary.refresh(containingContext);
+        oldLibrary.refresh(containingContext, []);
         _invalidateExportedDeclarations(invalidatedLibraries, oldLibrary);
       }
       if (newLibrary != null && newLibrary != oldLibrary) {
-        newLibrary.refresh(containingContext);
+        newLibrary.refresh(containingContext, []);
         _invalidateExportedDeclarations(invalidatedLibraries, newLibrary);
       }
     }
@@ -1164,7 +1180,7 @@
 
   String get uriStr => uri.toString();
 
-  void refresh(DeclarationsContext context) {
+  void refresh(DeclarationsContext context, List<String> partOrUriList) {
     var resource = tracker._resourceProvider.getFile(path);
 
     int modificationStamp;
@@ -1225,10 +1241,10 @@
 
     // Resolve exports and parts.
     for (var export in exports) {
-      export.file = _fileForRelativeUri(context, export.uri);
+      export.file = _fileForRelativeUri(context, partOrUriList, export.uri);
     }
     for (var part in parts) {
-      part.file = _fileForRelativeUri(context, part.uri);
+      part.file = _fileForRelativeUri(context, partOrUriList, part.uri);
     }
     exports.removeWhere((e) => e.file == null);
     parts.removeWhere((e) => e.file == null);
@@ -1753,9 +1769,13 @@
   }
 
   /// Return the [_File] for the given [relative] URI, maybe `null`.
-  _File? _fileForRelativeUri(DeclarationsContext context, Uri relative) {
+  _File? _fileForRelativeUri(
+    DeclarationsContext context,
+    List<String> partOrUriList,
+    Uri relative,
+  ) {
     var absoluteUri = resolveRelativeUri(uri, relative);
-    return tracker._getFileByUri(context, absoluteUri);
+    return tracker._getFileByUri(context, partOrUriList, absoluteUri);
   }
 
   void _putFileDeclarationsToByteStore(String contentKey) {
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
index 04e8cc2..f43dcc2 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
@@ -691,7 +691,10 @@
 }
 
 class Array<T extends NativeType> extends NativeType {
-  external const factory Array(int dimension1);
+  external const factory Array(int dimension1,
+      [int dimension2, int dimension3, int dimension4, int dimension5]);
+
+  external const factory Array.multi(List<int> dimensions);
 }
 
 extension StructPointer<T extends Struct> on Pointer<T> {
diff --git a/pkg/analyzer/test/src/diagnostics/non_sized_type_argument_test.dart b/pkg/analyzer/test/src/diagnostics/non_sized_type_argument_test.dart
index 85b1793..ec3f26d 100644
--- a/pkg/analyzer/test/src/diagnostics/non_sized_type_argument_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/non_sized_type_argument_test.dart
@@ -32,10 +32,10 @@
 
 class C extends Struct {
   @Array(8)
-  Array<Array<Uint8>> a0;
+  Array<Void> a0;
 }
 ''', [
-      error(FfiCode.NON_SIZED_TYPE_ARGUMENT, 59, 19),
+      error(FfiCode.NON_SIZED_TYPE_ARGUMENT, 59, 11),
     ]);
   }
 }
diff --git a/pkg/analyzer/test/src/diagnostics/size_annotation_dimensions_test.dart b/pkg/analyzer/test/src/diagnostics/size_annotation_dimensions_test.dart
new file mode 100644
index 0000000..8a53708
--- /dev/null
+++ b/pkg/analyzer/test/src/diagnostics/size_annotation_dimensions_test.dart
@@ -0,0 +1,67 @@
+// 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 'package:analyzer/src/dart/error/ffi_code.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import '../dart/resolution/context_collection_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(SizeAnnotationDimensions);
+  });
+}
+
+@reflectiveTest
+class SizeAnnotationDimensions extends PubPackageResolutionTest {
+  test_error_array_2_3() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+class C extends Struct {
+  @Array(8, 8)
+  Array<Array<Array<Uint8>>> a0;
+}
+''', [
+      error(FfiCode.SIZE_ANNOTATION_DIMENSIONS, 47, 12),
+    ]);
+  }
+
+  test_error_array_3_2() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+class C extends Struct {
+  @Array(8, 8, 8)
+  Array<Array<Uint8>> a0;
+}
+''', [
+      error(FfiCode.SIZE_ANNOTATION_DIMENSIONS, 47, 15),
+    ]);
+  }
+
+  test_error_multi_2_3() async {
+    await assertErrorsInCode(r'''
+import 'dart:ffi';
+
+class C extends Struct {
+  @Array.multi([8, 8])
+  Array<Array<Array<Uint8>>> a0;
+}
+''', [
+      error(FfiCode.SIZE_ANNOTATION_DIMENSIONS, 47, 20),
+    ]);
+  }
+
+  test_no_error() async {
+    await assertNoErrorsInCode(r'''
+import 'dart:ffi';
+
+class C extends Struct {
+  @Array(8, 8)
+  Array<Array<Uint8>> a0;
+}
+''');
+  }
+}
diff --git a/pkg/analyzer/test/src/diagnostics/test_all.dart b/pkg/analyzer/test/src/diagnostics/test_all.dart
index 11cd8e8..3916b75 100644
--- a/pkg/analyzer/test/src/diagnostics/test_all.dart
+++ b/pkg/analyzer/test/src/diagnostics/test_all.dart
@@ -571,6 +571,7 @@
 import 'set_element_type_not_assignable_test.dart'
     as set_element_type_not_assignable;
 import 'shared_deferred_prefix_test.dart' as shared_deferred_prefix;
+import 'size_annotation_dimensions_test.dart' as size_annotation_dimensions;
 import 'spread_expression_from_deferred_library_test.dart'
     as spread_expression_from_deferred_library;
 import 'static_access_to_instance_member_test.dart'
@@ -1044,6 +1045,7 @@
     sdk_version_ui_as_code_in_const_context.main();
     set_element_type_not_assignable.main();
     shared_deferred_prefix.main();
+    size_annotation_dimensions.main();
     spread_expression_from_deferred_library.main();
     static_access_to_instance_member.main();
     strict_raw_type.main();
diff --git a/pkg/compiler/lib/src/constants/constant_system.dart b/pkg/compiler/lib/src/constants/constant_system.dart
index 11ac356..b10c628 100644
--- a/pkg/compiler/lib/src/constants/constant_system.dart
+++ b/pkg/compiler/lib/src/constants/constant_system.dart
@@ -36,6 +36,7 @@
 const remainder = const RemainderOperation();
 const shiftLeft = const ShiftLeftOperation();
 const shiftRight = const ShiftRightOperation();
+const shiftRightUnsigned = const ShiftRightUnsignedOperation();
 const subtract = const SubtractOperation();
 const truncatingDivide = const TruncatingDivideOperation();
 const codeUnitAt = const CodeUnitAtOperation();
@@ -472,6 +473,24 @@
   apply(left, right) => left >> right;
 }
 
+class ShiftRightUnsignedOperation extends BinaryBitOperation {
+  @override
+  final String name = '>>>';
+
+  const ShiftRightUnsignedOperation();
+
+  @override
+  BigInt foldInts(BigInt left, BigInt right) {
+    if (right < BigInt.zero) return null;
+    return left.toUnsigned(32) >> right.toInt();
+  }
+
+  @override
+  apply(left, right) {
+    throw UnimplementedError('ShiftRightUnsignedOperation.apply');
+  }
+}
+
 abstract class BinaryBoolOperation implements BinaryOperation {
   const BinaryBoolOperation();
 
diff --git a/pkg/compiler/lib/src/elements/operators.dart b/pkg/compiler/lib/src/elements/operators.dart
index 4f3e95c..182511b 100644
--- a/pkg/compiler/lib/src/elements/operators.dart
+++ b/pkg/compiler/lib/src/elements/operators.dart
@@ -152,7 +152,7 @@
 
   /// The binary >>> operator.
   static const BinaryOperator SHRU =
-      const BinaryOperator._(BinaryOperatorKind.SHRU, '>>');
+      const BinaryOperator._(BinaryOperatorKind.SHRU, '>>>');
 
   /// The binary >= operator.
   static const BinaryOperator GTEQ =
diff --git a/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart b/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart
index f791743..02b8bab 100644
--- a/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart
+++ b/pkg/compiler/lib/src/inferrer/type_graph_nodes.dart
@@ -1307,6 +1307,13 @@
         }
         return null;
 
+      case '>>>':
+        if (isEmpty(argument)) return tryLater();
+        if (isUInt31(receiver)) {
+          return inferrer.types.uint31Type;
+        }
+        return null;
+
       case '&':
         if (isEmpty(argument)) return tryLater();
         if (isUInt31(receiver) || isUInt31(argument)) {
diff --git a/pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart b/pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart
index 61d417f..87e47b5 100644
--- a/pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart
+++ b/pkg/compiler/lib/src/ssa/invoke_dynamic_specializers.dart
@@ -67,8 +67,9 @@
       if (name == '/') return const DivideSpecializer();
       if (name == '~/') return const TruncatingDivideSpecializer();
       if (name == '%') return const ModuloSpecializer();
-      if (name == '>>') return const ShiftRightSpecializer();
       if (name == '<<') return const ShiftLeftSpecializer();
+      if (name == '>>') return const ShiftRightSpecializer();
+      if (name == '>>>') return const ShiftRightUnsignedSpecializer();
       if (name == '&') return const BitAndSpecializer();
       if (name == '|') return const BitOrSpecializer();
       if (name == '^') return const BitXorSpecializer();
@@ -1045,6 +1046,71 @@
   }
 }
 
+class ShiftRightUnsignedSpecializer extends BinaryBitOpSpecializer {
+  const ShiftRightUnsignedSpecializer();
+
+  @override
+  AbstractValue computeTypeFromInputTypes(HInvokeDynamic instruction,
+      GlobalTypeInferenceResults results, JClosedWorld closedWorld) {
+    HInstruction left = instruction.inputs[1];
+    if (left.isUInt32(closedWorld.abstractValueDomain).isDefinitelyTrue) {
+      return left.instructionType;
+    }
+    return super.computeTypeFromInputTypes(instruction, results, closedWorld);
+  }
+
+  @override
+  HInstruction tryConvertToBuiltin(
+      HInvokeDynamic instruction,
+      HGraph graph,
+      GlobalTypeInferenceResults results,
+      JCommonElements commonElements,
+      JClosedWorld closedWorld,
+      OptimizationTestLog log) {
+    HInstruction left = instruction.inputs[1];
+    HInstruction right = instruction.inputs[2];
+    if (left.isInteger(closedWorld.abstractValueDomain).isDefinitelyTrue) {
+      if (argumentLessThan32(right)) {
+        HInstruction converted =
+            newBuiltinVariant(instruction, results, closedWorld);
+        if (log != null) {
+          registerOptimization(log, instruction, converted);
+        }
+        return converted;
+      }
+      // Even if there is no builtin equivalent instruction, we know
+      // the instruction does not have any side effect, and that it
+      // can be GVN'ed.
+      clearAllSideEffects(instruction);
+      if (isPositive(right, closedWorld)) {
+        redirectSelector(instruction, '_shruOtherPositive', commonElements);
+        if (log != null) {
+          registerOptimization(log, instruction, null);
+        }
+      }
+    }
+    return null;
+  }
+
+  @override
+  HInstruction newBuiltinVariant(HInvokeDynamic instruction,
+      GlobalTypeInferenceResults results, JClosedWorld closedWorld) {
+    return HShiftRight(instruction.inputs[1], instruction.inputs[2],
+        computeTypeFromInputTypes(instruction, results, closedWorld));
+  }
+
+  @override
+  constant_system.BinaryOperation operation() {
+    return constant_system.shiftRightUnsigned;
+  }
+
+  @override
+  void registerOptimization(
+      OptimizationTestLog log, HInstruction original, HInstruction converted) {
+    log.registerShiftRightUnsigned(original, converted);
+  }
+}
+
 class BitOrSpecializer extends BinaryBitOpSpecializer {
   const BitOrSpecializer();
 
diff --git a/pkg/compiler/lib/src/ssa/logging.dart b/pkg/compiler/lib/src/ssa/logging.dart
index 4eff5a9..e2c63af 100644
--- a/pkg/compiler/lib/src/ssa/logging.dart
+++ b/pkg/compiler/lib/src/ssa/logging.dart
@@ -179,6 +179,12 @@
         'ShiftRight.${original.selector.name}');
   }
 
+  void registerShiftRightUnsigned(
+      HInvokeDynamic original, HShiftRight converted) {
+    _registerSpecializer(original, converted, 'ShiftRightUnsigned',
+        'ShiftRightUnsigned.${original.selector.name}');
+  }
+
   void registerBitOr(HInvokeDynamic original, HBitOr converted) {
     _registerSpecializer(original, converted, 'BitOr');
   }
diff --git a/pkg/compiler/test/codegen/codegen_test_helper.dart b/pkg/compiler/test/codegen/codegen_test_helper.dart
index f28f670..e4f9c73 100644
--- a/pkg/compiler/test/codegen/codegen_test_helper.dart
+++ b/pkg/compiler/test/codegen/codegen_test_helper.dart
@@ -33,7 +33,7 @@
       shards: 2,
       directory: 'data',
       skip: skip,
-      options: [Flags.soundNullSafety]);
+      options: [Flags.soundNullSafety, '--enable-experiment=triple-shift']);
 }
 
 runTests2(List<String> args, [int shardIndex]) {
diff --git a/pkg/compiler/test/codegen/data/shift_right_unsigned.dart b/pkg/compiler/test/codegen/data/shift_right_unsigned.dart
new file mode 100644
index 0000000..755d743
--- /dev/null
+++ b/pkg/compiler/test/codegen/data/shift_right_unsigned.dart
@@ -0,0 +1,117 @@
+// 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.
+
+//@dart=2.12
+
+/*member: main:ignore*/
+void main() {
+  for (var a in [false, true]) {
+    sink = cannotRecognize(a ? 10 : C());
+    sink = unspecialized(a ? -1 : 1);
+    sink = otherPositive2(a);
+    sink = shiftBySix(a);
+    sink = shiftByMasked(a, 9);
+    sink = shiftByMasked(a, -9);
+  }
+
+  sink = cannotConstantFold();
+  sink = constantFoldPositive();
+  sink = constantFoldNegative();
+  test6();
+}
+
+Object? sink;
+
+@pragma('dart2js:noInline')
+/*spec.member: cannotRecognize:function(thing) {
+  return H._asInt(J.$shru$n(thing, 1));
+}*/
+/*prod.member: cannotRecognize:function(thing) {
+  return J.$shru$n(thing, 1);
+}*/
+int cannotRecognize(dynamic thing) {
+  return thing >>> 1;
+}
+
+@pragma('dart2js:noInline')
+/*member: cannotConstantFold:function() {
+  return C.JSInt_methods.$shru(1, -1);
+}*/
+int cannotConstantFold() {
+  var a = 1;
+  return a >>> -1;
+}
+
+@pragma('dart2js:noInline')
+/*member: constantFoldPositive:function() {
+  return 25;
+}*/
+int constantFoldPositive() {
+  var a = 100;
+  return a >>> 2;
+}
+
+@pragma('dart2js:noInline')
+/*member: constantFoldNegative:function() {
+  return 3;
+}*/
+int constantFoldNegative() {
+  var a = -1;
+  return a >>> 30;
+}
+
+@pragma('dart2js:noInline')
+/*member: unspecialized:function(a) {
+  return C.JSInt_methods.$shru(1, a);
+}*/
+int unspecialized(int a) {
+  return 1 >>> a;
+}
+
+@pragma('dart2js:noInline')
+/*member: otherPositive2:function(param) {
+  return C.JSInt_methods._shruOtherPositive$1(1, param ? 1 : 2);
+}*/
+int otherPositive2(bool param) {
+  var a = param ? 1 : 2;
+  return 1 >>> a;
+}
+
+@pragma('dart2js:noInline')
+/*member: shiftBySix:function(param) {
+  return (param ? 4294967295 : -1) >>> 6;
+}*/
+int shiftBySix(bool param) {
+  var a = param ? 0xFFFFFFFF : -1;
+  return a >>> 6;
+}
+
+@pragma('dart2js:noInline')
+/*member: shiftByMasked:function(param1, shift) {
+  var a = param1 ? 4294967295 : 0;
+  return a >>> (shift & 31);
+}*/
+int shiftByMasked(bool param1, int shift) {
+  var a = param1 ? 0xFFFFFFFF : 0;
+  return a >>> (shift & 31);
+}
+
+@pragma('dart2js:noInline')
+/*member: otherPositive6:function(a, b) {
+  return C.JSInt_methods._shruOtherPositive$1(a, b);
+}*/
+int otherPositive6(int a, int b) {
+  return a >>> b;
+}
+
+void test6() {
+  sink = otherPositive6(1, 3);
+  sink = otherPositive6(0, 4);
+  sink = otherPositive6(-1, 2);
+}
+
+class C {
+  /*member: C.>>>:ignore*/
+  C operator >>>(int i) => this;
+}
diff --git a/pkg/compiler/test/codegen/shift_right_unsigned_test.dart b/pkg/compiler/test/codegen/shift_right_unsigned_test.dart
new file mode 100644
index 0000000..786bc71
--- /dev/null
+++ b/pkg/compiler/test/codegen/shift_right_unsigned_test.dart
@@ -0,0 +1,154 @@
+// Copyright (c) 2015, 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.11
+
+library shru_test;
+
+import 'dart:async';
+import 'package:async_helper/async_helper.dart';
+import '../helpers/compiler_helper.dart';
+
+const COMMON = r"""
+int g1 = 0, g2 = 0;
+int sink1 = 0, sink2 = 0;
+
+main() {
+  for (int i = 0; i < 0x100000000; i = i + (i >> 4) + 1) {
+    g1 = g2 = i;
+    sink1 = callFoo(i, 1 - i, i);
+    sink2 = callFoo(2 - i, i, 3 - i);
+  }
+}
+""";
+
+const tests = [
+  r"""
+// constant-fold positive
+int foo(int param) {
+  int a = 100;
+  int b = 2;
+  return a >>> b;
+  // present: 'return 25;'
+}
+""",
+
+  r"""
+// constant-fold negative
+int foo(int param) {
+  int a = -1;
+  int b = 30;
+  return a >>> b;
+  // present: 'return 3;'
+}
+  """,
+
+  r"""
+// base case
+int foo(int value, int shift) {
+  return value >>> shift;
+  // Default code pattern:
+  // present: 'return C.JSInt_methods.$shru(value, shift);'
+}
+int callFoo(int a, int b, int c) => foo(a, b);
+""",
+
+  r"""
+// shift by zero
+int foo(int param) {
+  return param >>> 0;
+  // present: 'return param >>> 0;'
+}
+  """,
+
+  r"""
+// shift by one
+int foo(int param) {
+  return param >>> 1;
+  // present: 'return param >>> 1;'
+}
+""",
+
+  r"""
+// shift masked into safe range
+int foo(int value, int shift) {
+  return value >>> (shift & 31);
+  // present: 'return value >>> (shift & 31);'
+}
+int callFoo(int a, int b, int c) => foo(a, b);
+""",
+
+  r"""
+// idempotent shift by zero
+int foo(int param) {
+  return param >>> 0 >>> 0 >>> 0;
+  // present: 'return param >>> 0;'
+}
+""",
+
+  r"""
+// idempotent shift by zero #2
+int foo(int param) {
+  return (param & 15) >>> 0;
+  // present: 'return param & 15;'
+}
+  """,
+
+// TODO(sra): shift-shift reduction.
+//  r"""
+//// shift-shift-reduction
+//int foo(int param) {
+//  return param >>> 1 >>> 2;
+//  // present: 'return param >>> 3'
+//}
+//""",
+
+  r"""
+// mask-shift to shift-mask reduction
+int foo(int param) {
+  return (param & 0xF0) >>> 4;
+  // present: 'return param >>> 4 & 15'
+}
+""",
+
+  r"""
+// mask-shift to shift-mask reduction enabling mask reduction
+int foo(int param) {
+  return (param & 0x7FFFFFFF) >>> 31;
+  // present: 'return 0;'
+}
+""",
+];
+
+main() {
+  runTests() async {
+    Future check(String test) {
+      String program = COMMON + '\n\n' + test;
+      if (!test.contains('callFoo')) {
+        program += 'int callFoo(int a, int b, int c) => foo(a);\n';
+      }
+      return compile(program,
+          entry: 'main',
+          methodName: 'foo',
+          disableTypeInference: false,
+          enableTripleShift: true,
+          check: checkerForAbsentPresent(test));
+    }
+
+    for (final test in tests) {
+      String name = 'unnamed';
+      if (test.startsWith('//')) {
+        final comment = test.split('\n').first.replaceFirst('//', '').trim();
+        if (comment.isNotEmpty) name = comment;
+      }
+      print('-- $name');
+      await check(test);
+    }
+  }
+
+  asyncTest(() async {
+    print('--test from kernel------------------------------------------------');
+    await runTests();
+  });
+}
diff --git a/pkg/compiler/test/helpers/compiler_helper.dart b/pkg/compiler/test/helpers/compiler_helper.dart
index a2ebf41..9b5006b 100644
--- a/pkg/compiler/test/helpers/compiler_helper.dart
+++ b/pkg/compiler/test/helpers/compiler_helper.dart
@@ -46,6 +46,7 @@
     bool disableInlining: true,
     bool disableTypeInference: true,
     bool omitImplicitChecks: true,
+    bool enableTripleShift: false, // TODO(30890): remove.
     bool enableVariance: false,
     void check(String generatedEntry),
     bool returnAll: false,
@@ -67,6 +68,9 @@
   if (disableInlining) {
     options.add(Flags.disableInlining);
   }
+  if (enableTripleShift) {
+    options.add('${Flags.enableLanguageExperiments}=triple-shift');
+  }
   if (enableVariance) {
     options.add('${Flags.enableLanguageExperiments}=variance');
   }
diff --git a/pkg/compiler/test/inference/data/shift_right_unsigned.dart b/pkg/compiler/test/inference/data/shift_right_unsigned.dart
new file mode 100644
index 0000000..5faf990
--- /dev/null
+++ b/pkg/compiler/test/inference/data/shift_right_unsigned.dart
@@ -0,0 +1,40 @@
+// 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.12
+
+/*member: main:[null]*/
+main() {
+  g1 = -1;
+  g1 = 2;
+  test1();
+  test2();
+  test3();
+  test4();
+}
+
+/*member: g1:[subclass=JSInt]*/
+int g1 = 0;
+
+/*member: test1:[exact=JSUInt31]*/
+test1() {
+  int a = 1234;
+  int b = 2;
+  return a /*invoke: [exact=JSUInt31]*/>>> b;
+}
+
+/*member: test2:[subclass=JSUInt32]*/
+test2() {
+  return g1 /*invoke: [subclass=JSInt]*/>>> g1;
+}
+
+/*member: test3:[subclass=JSUInt32]*/
+test3() {
+  return g1 /*invoke: [subclass=JSInt]*/>>> 1;
+}
+
+/*member: test4:[exact=JSUInt31]*/
+test4() {
+  return 10 /*invoke: [exact=JSUInt31]*/>>> g1;
+}
diff --git a/pkg/compiler/test/inference/inference_test_helper.dart b/pkg/compiler/test/inference/inference_test_helper.dart
index d049559..701d33d 100644
--- a/pkg/compiler/test/inference/inference_test_helper.dart
+++ b/pkg/compiler/test/inference/inference_test_helper.dart
@@ -32,7 +32,7 @@
     await checkTests(dataDir, const TypeMaskDataComputer(),
         forUserLibrariesOnly: true,
         args: args,
-        options: [stopAfterTypeInference],
+        options: [stopAfterTypeInference, '--enable-experiment=triple-shift'],
         testedConfigs: allInternalConfigs,
         skip: skip,
         shardIndex: shardIndex ?? 0,
diff --git a/pkg/compiler/test/optimization/data/shift_right_unsigned.dart b/pkg/compiler/test/optimization/data/shift_right_unsigned.dart
new file mode 100644
index 0000000..6a8aaf3
--- /dev/null
+++ b/pkg/compiler/test/optimization/data/shift_right_unsigned.dart
@@ -0,0 +1,53 @@
+// 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.
+
+main() {
+  shru1(1, 1);
+  shru1(2, 2);
+  shru1(-1, -1);
+
+  shruOtherInferredPositive(1, 1);
+  shruOtherInferredPositive(99, 99);
+  shruOtherInferredPositive(-1, 2);
+
+  shruSix(1);
+  shruSix(-1);
+
+  shruMaskedCount(1, 1);
+  shruMaskedCount(999, 999);
+  shruMaskedCount(-1, -2);
+
+  shruMaskedCount2(1, 1);
+  shruMaskedCount2(999, 999);
+  shruMaskedCount2(-1, -2);
+}
+
+@pragma('dart2js:noInline')
+shru1(a, b) {
+  return a >>> b;
+}
+
+@pragma('dart2js:noInline')
+/*member: shruOtherInferredPositive:Specializer=[ShiftRightUnsigned._shruOtherPositive]*/
+shruOtherInferredPositive(a, b) {
+  return a >>> b;
+}
+
+@pragma('dart2js:noInline')
+/*member: shruSix:Specializer=[ShiftRightUnsigned]*/
+shruSix(int a) {
+  return a >>> 6;
+}
+
+@pragma('dart2js:noInline')
+/*member: shruMaskedCount:Specializer=[BitAnd,ShiftRightUnsigned]*/
+shruMaskedCount(int a, int b) {
+  return a >>> (b & 31);
+}
+
+@pragma('dart2js:noInline')
+/*member: shruMaskedCount2:Specializer=[BitAnd,ShiftRightUnsigned._shruOtherPositive]*/
+shruMaskedCount2(int a, int b) {
+  return a >>> (b & 127);
+}
diff --git a/pkg/compiler/test/optimization/optimization_test.dart b/pkg/compiler/test/optimization/optimization_test.dart
index b5f2987..baf38d0 100644
--- a/pkg/compiler/test/optimization/optimization_test.dart
+++ b/pkg/compiler/test/optimization/optimization_test.dart
@@ -27,7 +27,8 @@
     Directory dataDir = new Directory.fromUri(Platform.script.resolve('data'));
     bool strict = args.contains('-s');
     await checkTests(dataDir, new OptimizationDataComputer(strict: strict),
-        options: [Flags.disableInlining], args: args);
+        options: [Flags.disableInlining, '--enable-experiment=triple-shift'],
+        args: args);
   });
 }
 
diff --git a/pkg/front_end/lib/src/api_unstable/vm.dart b/pkg/front_end/lib/src/api_unstable/vm.dart
index 5fe07cd..80da869 100644
--- a/pkg/front_end/lib/src/api_unstable/vm.dart
+++ b/pkg/front_end/lib/src/api_unstable/vm.dart
@@ -65,6 +65,7 @@
         templateFfiFieldNull,
         templateFfiNotStatic,
         templateFfiSizeAnnotation,
+        templateFfiSizeAnnotationDimensions,
         templateFfiStructGeneric,
         templateFfiTypeInvalid,
         templateFfiTypeMismatch;
diff --git a/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart b/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
index 127d61d..bd8a55f 100644
--- a/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
+++ b/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
@@ -4732,6 +4732,43 @@
 const Template<
         Message Function(
             String name, DartType _type, bool isNonNullableByDefault)>
+    templateUndefinedExtensionGetter = const Template<
+            Message Function(
+                String name, DartType _type, bool isNonNullableByDefault)>(
+        messageTemplate:
+            r"""The getter '#name' isn't defined for the extension '#type'.""",
+        tipTemplate:
+            r"""Try correcting the name to the name of an existing getter, or defining a getter or field named '#name'.""",
+        withArguments: _withArgumentsUndefinedExtensionGetter);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<
+    Message Function(String name, DartType _type,
+        bool isNonNullableByDefault)> codeUndefinedExtensionGetter = const Code<
+    Message Function(String name, DartType _type, bool isNonNullableByDefault)>(
+  "UndefinedExtensionGetter",
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsUndefinedExtensionGetter(
+    String name, DartType _type, bool isNonNullableByDefault) {
+  if (name.isEmpty) throw 'No name provided';
+  name = demangleMixinApplicationName(name);
+  TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
+  List<Object> typeParts = labeler.labelType(_type);
+  String type = typeParts.join();
+  return new Message(codeUndefinedExtensionGetter,
+      message:
+          """The getter '${name}' isn't defined for the extension '${type}'.""" +
+              labeler.originMessages,
+      tip: """Try correcting the name to the name of an existing getter, or defining a getter or field named '${name}'.""",
+      arguments: {'name': name, 'type': _type});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<
+        Message Function(
+            String name, DartType _type, bool isNonNullableByDefault)>
     templateUndefinedExtensionMethod = const Template<
             Message Function(
                 String name, DartType _type, bool isNonNullableByDefault)>(
@@ -4767,6 +4804,82 @@
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<
+        Message Function(
+            String name, DartType _type, bool isNonNullableByDefault)>
+    templateUndefinedExtensionOperator = const Template<
+            Message Function(
+                String name, DartType _type, bool isNonNullableByDefault)>(
+        messageTemplate:
+            r"""The operator '#name' isn't defined for the extension '#type'.""",
+        tipTemplate:
+            r"""Try correcting the operator to an existing operator, or defining a '#name' operator.""",
+        withArguments: _withArgumentsUndefinedExtensionOperator);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<
+        Message Function(
+            String name, DartType _type, bool isNonNullableByDefault)>
+    codeUndefinedExtensionOperator = const Code<
+        Message Function(
+            String name, DartType _type, bool isNonNullableByDefault)>(
+  "UndefinedExtensionOperator",
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsUndefinedExtensionOperator(
+    String name, DartType _type, bool isNonNullableByDefault) {
+  if (name.isEmpty) throw 'No name provided';
+  name = demangleMixinApplicationName(name);
+  TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
+  List<Object> typeParts = labeler.labelType(_type);
+  String type = typeParts.join();
+  return new Message(codeUndefinedExtensionOperator,
+      message:
+          """The operator '${name}' isn't defined for the extension '${type}'.""" +
+              labeler.originMessages,
+      tip: """Try correcting the operator to an existing operator, or defining a '${name}' operator.""",
+      arguments: {'name': name, 'type': _type});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<
+        Message Function(
+            String name, DartType _type, bool isNonNullableByDefault)>
+    templateUndefinedExtensionSetter = const Template<
+            Message Function(
+                String name, DartType _type, bool isNonNullableByDefault)>(
+        messageTemplate:
+            r"""The setter '#name' isn't defined for the extension '#type'.""",
+        tipTemplate:
+            r"""Try correcting the name to the name of an existing setter, or defining a setter or field named '#name'.""",
+        withArguments: _withArgumentsUndefinedExtensionSetter);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<
+    Message Function(String name, DartType _type,
+        bool isNonNullableByDefault)> codeUndefinedExtensionSetter = const Code<
+    Message Function(String name, DartType _type, bool isNonNullableByDefault)>(
+  "UndefinedExtensionSetter",
+);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsUndefinedExtensionSetter(
+    String name, DartType _type, bool isNonNullableByDefault) {
+  if (name.isEmpty) throw 'No name provided';
+  name = demangleMixinApplicationName(name);
+  TypeLabeler labeler = new TypeLabeler(isNonNullableByDefault);
+  List<Object> typeParts = labeler.labelType(_type);
+  String type = typeParts.join();
+  return new Message(codeUndefinedExtensionSetter,
+      message:
+          """The setter '${name}' isn't defined for the extension '${type}'.""" +
+              labeler.originMessages,
+      tip: """Try correcting the name to the name of an existing setter, or defining a setter or field named '${name}'.""",
+      arguments: {'name': name, 'type': _type});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<
     Message Function(
         String name,
         DartType _type,
diff --git a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
index 7c9aad9..e414321 100644
--- a/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/constant_evaluator.dart
@@ -809,7 +809,7 @@
   }
 }
 
-class ConstantEvaluator extends ExpressionVisitor<Constant> {
+class ConstantEvaluator implements ExpressionVisitor<Constant> {
   final ConstantsBackend backend;
   final NumberSemantics numberSemantics;
   ConstantIntFolder intFolder;
@@ -3198,6 +3198,60 @@
       node = node.parent;
     }
   }
+
+  @override
+  Constant defaultBasicLiteral(BasicLiteral node) => defaultExpression(node);
+
+  @override
+  Constant visitAwaitExpression(AwaitExpression node) =>
+      defaultExpression(node);
+
+  @override
+  Constant visitBlockExpression(BlockExpression node) =>
+      defaultExpression(node);
+
+  @override
+  Constant visitDynamicSet(DynamicSet node) => defaultExpression(node);
+
+  @override
+  Constant visitInstanceGetterInvocation(InstanceGetterInvocation node) =>
+      defaultExpression(node);
+
+  @override
+  Constant visitInstanceSet(InstanceSet node) => defaultExpression(node);
+
+  @override
+  Constant visitLoadLibrary(LoadLibrary node) => defaultExpression(node);
+
+  @override
+  Constant visitPropertySet(PropertySet node) => defaultExpression(node);
+
+  @override
+  Constant visitRethrow(Rethrow node) => defaultExpression(node);
+
+  @override
+  Constant visitStaticSet(StaticSet node) => defaultExpression(node);
+
+  @override
+  Constant visitSuperMethodInvocation(SuperMethodInvocation node) =>
+      defaultExpression(node);
+
+  @override
+  Constant visitSuperPropertyGet(SuperPropertyGet node) =>
+      defaultExpression(node);
+
+  @override
+  Constant visitSuperPropertySet(SuperPropertySet node) =>
+      defaultExpression(node);
+
+  @override
+  Constant visitThisExpression(ThisExpression node) => defaultExpression(node);
+
+  @override
+  Constant visitThrow(Throw node) => defaultExpression(node);
+
+  @override
+  Constant visitVariableSet(VariableSet node) => defaultExpression(node);
 }
 
 class StatementConstantEvaluator extends StatementVisitor<ExecutionStatus> {
diff --git a/pkg/front_end/lib/src/fasta/problems.dart b/pkg/front_end/lib/src/fasta/problems.dart
index db9552f..cd06b1f 100644
--- a/pkg/front_end/lib/src/fasta/problems.dart
+++ b/pkg/front_end/lib/src/fasta/problems.dart
@@ -45,9 +45,10 @@
 ///
 /// Before printing the message, the string `"Internal error: "` is prepended.
 dynamic internalProblem(Message message, int charOffset, Uri uri) {
-  throw CompilerContext.current.format(
-      message.withLocation(uri, charOffset, noLength),
-      Severity.internalProblem);
+  throw CompilerContext.current
+      .format(message.withLocation(uri, charOffset, noLength),
+          Severity.internalProblem)
+      .plain;
 }
 
 dynamic unimplemented(String what, int charOffset, Uri uri) {
diff --git a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
index d01bde9..be88ff0 100644
--- a/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
+++ b/pkg/front_end/lib/src/fasta/type_inference/type_inferrer.dart
@@ -883,30 +883,46 @@
   ObjectAccessTarget _findDirectExtensionMember(
       ExtensionType receiverType, Name name, int fileOffset,
       {ObjectAccessTarget defaultTarget}) {
-    Member targetMethod;
+    Member targetMember;
     Member targetTearoff;
+    ProcedureKind targetKind;
     for (ExtensionMemberDescriptor descriptor
         in receiverType.extensionNode.members) {
       if (descriptor.name == name) {
         switch (descriptor.kind) {
           case ExtensionMemberKind.Method:
-            targetMethod = descriptor.member.asMember;
+            targetMember = descriptor.member.asMember;
+            targetTearoff ??= targetMember;
+            targetKind = ProcedureKind.Method;
             break;
           case ExtensionMemberKind.TearOff:
             targetTearoff = descriptor.member.asMember;
             break;
+          case ExtensionMemberKind.Getter:
+            targetMember = descriptor.member.asMember;
+            targetTearoff = null;
+            targetKind = ProcedureKind.Getter;
+            break;
+          case ExtensionMemberKind.Setter:
+            targetMember = descriptor.member.asMember;
+            targetTearoff = null;
+            targetKind = ProcedureKind.Setter;
+            break;
+          case ExtensionMemberKind.Operator:
+            targetMember = descriptor.member.asMember;
+            targetTearoff = null;
+            targetKind = ProcedureKind.Operator;
+            break;
           default:
-            unhandled("${descriptor.kind}", "findInterfaceMember", fileOffset,
-                library.fileUri);
+            unhandled("${descriptor.kind}", "_findDirectExtensionMember",
+                fileOffset, library.fileUri);
         }
       }
     }
-    if (targetMethod != null) {
+    if (targetMember != null) {
+      assert(targetKind != null);
       return new ObjectAccessTarget.extensionMember(
-          targetMethod,
-          targetTearoff ?? targetMethod,
-          ProcedureKind.Method,
-          receiverType.typeArguments);
+          targetMember, targetTearoff, targetKind, receiverType.typeArguments);
     } else {
       return defaultTarget;
     }
@@ -4134,13 +4150,19 @@
       return engine.forest
           .createPropertyGet(fileOffset, receiver, propertyName);
     } else {
+      Template<Message Function(String, DartType, bool)> templateMissing;
+      if (receiverType is ExtensionType) {
+        templateMissing = templateUndefinedExtensionGetter;
+      } else {
+        templateMissing = templateUndefinedGetter;
+      }
       return _reportMissingOrAmbiguousMember(
           fileOffset,
           propertyName.text.length,
           receiverType,
           propertyName,
           extensionAccessCandidates,
-          templateUndefinedGetter,
+          templateMissing,
           templateAmbiguousExtensionProperty);
     }
   }
@@ -4155,13 +4177,19 @@
           fileOffset, receiver, propertyName, value,
           forEffect: forEffect);
     } else {
+      Template<Message Function(String, DartType, bool)> templateMissing;
+      if (receiverType is ExtensionType) {
+        templateMissing = templateUndefinedExtensionSetter;
+      } else {
+        templateMissing = templateUndefinedSetter;
+      }
       return _reportMissingOrAmbiguousMember(
           fileOffset,
           propertyName.text.length,
           receiverType,
           propertyName,
           extensionAccessCandidates,
-          templateUndefinedSetter,
+          templateMissing,
           templateAmbiguousExtensionProperty);
     }
   }
@@ -4172,13 +4200,19 @@
     if (isTopLevel) {
       return engine.forest.createIndexGet(fileOffset, receiver, index);
     } else {
+      Template<Message Function(String, DartType, bool)> templateMissing;
+      if (receiverType is ExtensionType) {
+        templateMissing = templateUndefinedExtensionOperator;
+      } else {
+        templateMissing = templateUndefinedOperator;
+      }
       return _reportMissingOrAmbiguousMember(
           fileOffset,
           noLength,
           receiverType,
           indexGetName,
           extensionAccessCandidates,
-          templateUndefinedOperator,
+          templateMissing,
           templateAmbiguousExtensionOperator);
     }
   }
@@ -4192,13 +4226,19 @@
       return engine.forest.createIndexSet(fileOffset, receiver, index, value,
           forEffect: forEffect);
     } else {
+      Template<Message Function(String, DartType, bool)> templateMissing;
+      if (receiverType is ExtensionType) {
+        templateMissing = templateUndefinedExtensionOperator;
+      } else {
+        templateMissing = templateUndefinedOperator;
+      }
       return _reportMissingOrAmbiguousMember(
           fileOffset,
           noLength,
           receiverType,
           indexSetName,
           extensionAccessCandidates,
-          templateUndefinedOperator,
+          templateMissing,
           templateAmbiguousExtensionOperator);
     }
   }
@@ -4211,13 +4251,19 @@
       return engine.forest.createMethodInvocation(fileOffset, left, binaryName,
           engine.forest.createArguments(fileOffset, <Expression>[right]));
     } else {
+      Template<Message Function(String, DartType, bool)> templateMissing;
+      if (leftType is ExtensionType) {
+        templateMissing = templateUndefinedExtensionOperator;
+      } else {
+        templateMissing = templateUndefinedOperator;
+      }
       return _reportMissingOrAmbiguousMember(
           fileOffset,
           binaryName.text.length,
           leftType,
           binaryName,
           extensionAccessCandidates,
-          templateUndefinedOperator,
+          templateMissing,
           templateAmbiguousExtensionOperator);
     }
   }
@@ -4229,13 +4275,19 @@
       return new UnaryExpression(unaryName, expression)
         ..fileOffset = fileOffset;
     } else {
+      Template<Message Function(String, DartType, bool)> templateMissing;
+      if (expressionType is ExtensionType) {
+        templateMissing = templateUndefinedExtensionOperator;
+      } else {
+        templateMissing = templateUndefinedOperator;
+      }
       return _reportMissingOrAmbiguousMember(
           fileOffset,
           unaryName == unaryMinusName ? 1 : unaryName.text.length,
           expressionType,
           unaryName,
           extensionAccessCandidates,
-          templateUndefinedOperator,
+          templateMissing,
           templateAmbiguousExtensionOperator);
     }
   }
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index 9c120af..7d556ba 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -330,6 +330,7 @@
 FfiFieldNull/analyzerCode: Fail
 FfiNotStatic/analyzerCode: Fail
 FfiSizeAnnotation/analyzerCode: Fail
+FfiSizeAnnotationDimensions/analyzerCode: Fail
 FfiStructAnnotation/analyzerCode: Fail
 FfiStructGeneric/analyzerCode: Fail
 FfiTypeInvalid/analyzerCode: Fail
@@ -772,8 +773,14 @@
 TypedefNullableType/analyzerCode: Fail
 TypedefTypeVariableNotConstructor/analyzerCode: Fail # Feature not yet enabled by default.
 TypedefTypeVariableNotConstructor/example: Fail # Feature not yet enabled by default.
+UndefinedExtensionGetter/analyzerCode: Fail
+UndefinedExtensionGetter/example: Fail
 UndefinedExtensionMethod/analyzerCode: Fail
 UndefinedExtensionMethod/example: Fail
+UndefinedExtensionOperator/analyzerCode: Fail
+UndefinedExtensionOperator/example: Fail
+UndefinedExtensionSetter/analyzerCode: Fail
+UndefinedExtensionSetter/example: Fail
 UnexpectedToken/part_wrapped_script1: Fail
 UnexpectedToken/script1: Fail
 UnmatchedToken/part_wrapped_script1: Fail
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 36d6bcc..ec1663f 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -3491,10 +3491,22 @@
       c + 0;
     }
 
+UndefinedExtensionGetter:
+  template: "The getter '#name' isn't defined for the extension '#type'."
+  tip: "Try correcting the name to the name of an existing getter, or defining a getter or field named '#name'."
+
+UndefinedExtensionSetter:
+  template: "The setter '#name' isn't defined for the extension '#type'."
+  tip: "Try correcting the name to the name of an existing setter, or defining a setter or field named '#name'."
+
 UndefinedExtensionMethod:
   template: "The method '#name' isn't defined for the extension '#type'."
   tip: "Try correcting the name to the name of an existing method, or defining a method name '#name'."
 
+UndefinedExtensionOperator:
+  template: "The operator '#name' isn't defined for the extension '#type'."
+  tip: "Try correcting the operator to an existing operator, or defining a '#name' operator."
+
 AmbiguousExtensionMethod:
   template: "The method '#name' is defined in multiple extensions for '#type' and neither is more specific."
   tip: "Try using an explicit extension application of the wanted extension or hiding unwanted extensions from scope."
@@ -4272,6 +4284,11 @@
   template: "Field '#name' must have exactly one 'Array' annotation."
   external: test/ffi_test.dart
 
+FfiSizeAnnotationDimensions:
+  # Used by dart:ffi
+  template: "Field '#name' must have an 'Array' annotation that matches the dimensions."
+  external: test/ffi_test.dart
+
 FfiStructGeneric:
   # Used by dart:ffi
   template: "Struct '#name' should not be generic."
diff --git a/pkg/front_end/test/fasta/testing/suite.dart b/pkg/front_end/test/fasta/testing/suite.dart
index 3b69a38..d4cc164 100644
--- a/pkg/front_end/test/fasta/testing/suite.dart
+++ b/pkg/front_end/test/fasta/testing/suite.dart
@@ -159,6 +159,8 @@
         StdioProcess;
 
 import 'package:vm/target/vm.dart' show VmTarget;
+import 'package:vm/transformations/type_flow/transformer.dart' as type_flow;
+import 'package:vm/transformations/pragma.dart' as type_flow;
 
 import '../../utils/kernel_chain.dart'
     show
@@ -312,10 +314,6 @@
   final bool semiFuzz;
   final bool verify;
   final bool soundNullSafety;
-  final Map<Component, KernelTarget> componentToTarget =
-      <Component, KernelTarget>{};
-  final Map<Component, List<Iterable<String>>> componentToDiagnostics =
-      <Component, List<Iterable<String>>>{};
   final Uri platformBinaries;
   final Map<UriConfiguration, UriTranslator> _uriTranslators = {};
   final Map<Uri, FolderOptions> _folderOptions = {};
@@ -393,6 +391,7 @@
     }
     if (fullCompile) {
       steps.add(const Transform());
+      steps.add(const Verify(true));
       steps.add(const StressConstantEvaluatorStep());
       if (!ignoreExpectations) {
         steps.add(new MatchExpectation("$fullPrefix.transformed.expect",
@@ -678,18 +677,27 @@
 
   Expectation get verificationError => expectationSet["VerificationError"];
 
-  Future<Component> loadPlatform(Target target, NnbdMode nnbdMode) async {
+  Uri _getPlatformUri(Target target, NnbdMode nnbdMode) {
     String fileName = computePlatformDillName(
         target,
         nnbdMode,
         () => throw new UnsupportedError(
             "No platform dill for target '${target.name}' with $nnbdMode."));
-    Uri uri = platformBinaries.resolve(fileName);
+    return platformBinaries.resolve(fileName);
+  }
+
+  Future<Component> loadPlatform(Target target, NnbdMode nnbdMode) async {
+    Uri uri = _getPlatformUri(target, nnbdMode);
     return _platforms.putIfAbsent(uri, () {
       return loadComponentFromBytes(new File.fromUri(uri).readAsBytesSync());
     });
   }
 
+  void clearPlatformCache(Target target, NnbdMode nnbdMode) async {
+    Uri uri = _getPlatformUri(target, nnbdMode);
+    _platforms.remove(uri);
+  }
+
   @override
   Result processTestResult(
       TestDescription description, Result result, bool last) {
@@ -715,7 +723,9 @@
 
   static Future<FastaContext> create(
       Chain suite, Map<String, String> environment) async {
-    Uri vm = Uri.base.resolveUri(new Uri.file(Platform.resolvedExecutable));
+    String resolvedExecutable = Platform.environment['resolvedExecutable'] ??
+        Platform.resolvedExecutable;
+    Uri vm = Uri.base.resolveUri(new Uri.file(resolvedExecutable));
     Map<ExperimentalFlag, bool> experimentalFlags = <ExperimentalFlag, bool>{};
 
     void addForcedExperimentalFlag(String name, ExperimentalFlag flag) {
@@ -812,11 +822,12 @@
         }
         return new Result<ComponentResult>(
             result, runResult.outcome, runResult.error);
+      case "aot":
       case "none":
       case "noneWithJs":
       case "dart2js":
       case "dartdevc":
-        // TODO(johnniwinther): Support running dart2js and/or dartdevc.
+        // TODO(johnniwinther): Support running vm aot, dart2js and/or dartdevc.
         return pass(result);
       default:
         throw new ArgumentError(
@@ -1648,6 +1659,9 @@
     case "vm":
       target = new TestVmTarget(targetFlags);
       break;
+    case "aot":
+      target = new TestVmAotTarget(targetFlags);
+      break;
     case "none":
       target = new NoneTarget(targetFlags);
       break;
@@ -1781,10 +1795,6 @@
       await instrumentation.loadExpectations(description.uri);
       sourceTarget.loader.instrumentation = instrumentation;
       Component p = await sourceTarget.buildOutlines();
-      context.componentToTarget.clear();
-      context.componentToTarget[p] = sourceTarget;
-      context.componentToDiagnostics.clear();
-      context.componentToDiagnostics[p] = compilationSetup.errors;
       Set<Uri> userLibraries =
           createUserLibrariesImportUriSet(p, sourceTarget.uriTranslator);
       if (fullCompile) {
@@ -1799,7 +1809,7 @@
           } else {
             return new Result<ComponentResult>(
                 new ComponentResult(description, p, userLibraries,
-                    compilationSetup.options, sourceTarget),
+                    compilationSetup, sourceTarget),
                 context.expectationSet["InstrumentationMismatch"],
                 instrumentation.problemsAsString,
                 autoFixCommand: '${UPDATE_COMMENTS}=true',
@@ -1807,8 +1817,8 @@
           }
         }
       }
-      return pass(new ComponentResult(description, p, userLibraries,
-          compilationSetup.options, sourceTarget));
+      return pass(new ComponentResult(
+          description, p, userLibraries, compilationSetup, sourceTarget));
     });
   }
 
@@ -1850,8 +1860,7 @@
       ComponentResult result, FastaContext context) async {
     return await CompilerContext.runWithOptions(result.options, (_) async {
       Component component = result.component;
-      KernelTarget sourceTarget = context.componentToTarget[component];
-      context.componentToTarget.remove(component);
+      KernelTarget sourceTarget = result.sourceTarget;
       Target backendTarget = sourceTarget.backendTarget;
       if (backendTarget is TestTarget) {
         backendTarget.performModularTransformations = true;
@@ -1872,7 +1881,18 @@
             context.expectationSet["TransformVerificationError"],
             errors.join('\n'));
       }
-      return pass(result);
+      if (backendTarget is TestTarget &&
+          backendTarget.hasGlobalTransformation) {
+        component =
+            backendTarget.performGlobalTransformations(sourceTarget, component);
+        // Clear the currently cached platform since the global transformation
+        // might have modified it.
+        context.clearPlatformCache(
+            backendTarget, result.compilationSetup.options.nnbdMode);
+      }
+
+      return pass(new ComponentResult(result.description, component,
+          result.userLibraries, result.compilationSetup, sourceTarget));
     });
   }
 }
@@ -1973,12 +1993,34 @@
           logger: logger);
     }
   }
+
+  bool get hasGlobalTransformation => false;
+
+  Component performGlobalTransformations(
+          KernelTarget kernelTarget, Component component) =>
+      component;
 }
 
 class TestVmTarget extends VmTarget with TestTarget {
   TestVmTarget(TargetFlags flags) : super(flags);
 }
 
+class TestVmAotTarget extends TestVmTarget {
+  TestVmAotTarget(TargetFlags flags) : super(flags);
+
+  @override
+  bool get hasGlobalTransformation => true;
+
+  @override
+  Component performGlobalTransformations(
+      KernelTarget kernelTarget, Component component) {
+    return type_flow.transformComponent(
+        this, kernelTarget.loader.coreTypes, component,
+        matcher: new type_flow.ConstantPragmaAnnotationParser(
+            kernelTarget.loader.coreTypes));
+  }
+}
+
 class EnsureNoErrors
     extends Step<ComponentResult, ComponentResult, FastaContext> {
   const EnsureNoErrors();
@@ -1987,8 +2029,7 @@
 
   Future<Result<ComponentResult>> run(
       ComponentResult result, FastaContext context) async {
-    List<Iterable<String>> errors =
-        context.componentToDiagnostics[result.component];
+    List<Iterable<String>> errors = result.compilationSetup.errors;
     return errors.isEmpty
         ? pass(result)
         : fail(
@@ -2009,7 +2050,7 @@
     Component component = result.component;
     Uri uri =
         component.uriToSource.keys.firstWhere((uri) => uri?.scheme == "file");
-    KernelTarget target = context.componentToTarget[component];
+    KernelTarget target = result.sourceTarget;
     ClassHierarchyBuilder hierarchy = target.loader.builderHierarchy;
     StringBuffer sb = new StringBuffer();
     for (ClassHierarchyNode node in hierarchy.nodes.values) {
diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt
index 9a5012d..25e141b 100644
--- a/pkg/front_end/test/spell_checking_list_common.txt
+++ b/pkg/front_end/test/spell_checking_list_common.txt
@@ -857,6 +857,9 @@
 digit
 digits
 dill
+dimension
+dimensional
+dimensions
 dir
 direct
 direction
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index 59a96f5..b3ade10 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -33,6 +33,7 @@
 annoying
 anon
 aoo
+aot
 approval
 approximation
 arbitrarily
@@ -98,6 +99,7 @@
 c59cdee365b94ce066344840f9e3412d642019b
 ca
 cafebabe
+calloc
 camel
 capitalized
 causal
diff --git a/pkg/front_end/test/utils/kernel_chain.dart b/pkg/front_end/test/utils/kernel_chain.dart
index 5afe941..f7b52fc 100644
--- a/pkg/front_end/test/utils/kernel_chain.dart
+++ b/pkg/front_end/test/utils/kernel_chain.dart
@@ -61,6 +61,8 @@
         Step,
         TestDescription;
 
+import '../fasta/testing/suite.dart' show CompilationSetup;
+
 final Uri platformBinariesLocation = computePlatformBinariesLocation();
 
 abstract class MatchContext implements ChainContext {
@@ -241,8 +243,7 @@
 
     StringBuffer buffer = new StringBuffer();
 
-    List<Iterable<String>> errors =
-        (context as dynamic).componentToDiagnostics[component];
+    List<Iterable<String>> errors = result.compilationSetup.errors;
     Set<String> reportedErrors = <String>{};
     for (Iterable<String> message in errors) {
       reportedErrors.add(message.join('\n'));
@@ -420,8 +421,13 @@
     Uri uri = tmp.uri.resolve("generated.dill");
     File generated = new File.fromUri(uri);
     IOSink sink = generated.openWrite();
-    result = new ComponentResult(result.description, result.component,
-        result.userLibraries, result.options, result.sourceTarget, uri);
+    result = new ComponentResult(
+        result.description,
+        result.component,
+        result.userLibraries,
+        result.compilationSetup,
+        result.sourceTarget,
+        uri);
     try {
       new BinaryPrinter(sink).writeComponentFile(component);
     } catch (e, s) {
@@ -517,12 +523,12 @@
   final Component component;
   final Set<Uri> userLibraries;
   final Uri outputUri;
-  final ProcessedOptions options;
+  final CompilationSetup compilationSetup;
   final KernelTarget sourceTarget;
   final List<String> extraConstantStrings = [];
 
   ComponentResult(this.description, this.component, this.userLibraries,
-      this.options, this.sourceTarget,
+      this.compilationSetup, this.sourceTarget,
       [this.outputUri]);
 
   bool isUserLibrary(Library library) {
@@ -532,4 +538,6 @@
   bool isUserLibraryImportUri(Uri importUri) {
     return userLibraries.contains(importUri);
   }
+
+  ProcessedOptions get options => compilationSetup.options;
 }
diff --git a/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart
new file mode 100644
index 0000000..d2ee09c
--- /dev/null
+++ b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart
@@ -0,0 +1,12 @@
+// 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 'main_lib.dart';
+
+main() {
+  List list = [];
+  if (list.isNotEmpty) {
+    new Class().method(null as dynamic);
+  }
+}
diff --git a/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.strong.expect b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.strong.expect
new file mode 100644
index 0000000..09282c5
--- /dev/null
+++ b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.strong.expect
@@ -0,0 +1,49 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+static method main() → dynamic {
+  core::List<dynamic> list = <dynamic>[];
+  if(list.{core::Iterable::isNotEmpty}) {
+    new mai::Class::•().{mai::Class::method}((null as{ForNonNullableByDefault} dynamic) as{TypeError,ForDynamic,ForNonNullableByDefault} mai::Enum);
+  }
+}
+
+library /*isNonNullableByDefault*/;
+import self as mai;
+import "dart:core" as core;
+
+class Enum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<mai::Enum> values = #C4;
+  static const field mai::Enum a = #C3;
+  const constructor •(core::int index, core::String _name) → mai::Enum
+    : mai::Enum::index = index, mai::Enum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{mai::Enum::_name};
+}
+class Class extends core::Object {
+  synthetic constructor •() → mai::Class
+    : super core::Object::•()
+    ;
+  method method(mai::Enum e) → core::int
+    return e.{mai::Enum::index};
+}
+
+constants  {
+  #C1 = 0
+  #C2 = "Enum.a"
+  #C3 = mai::Enum {index:#C1, _name:#C2}
+  #C4 = <mai::Enum>[#C3]
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///main_lib.dart:
+- Enum. (from org-dartlang-testcase:///main_lib.dart:5:6)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.strong.transformed.expect b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.strong.transformed.expect
new file mode 100644
index 0000000..24d6d3b
--- /dev/null
+++ b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.strong.transformed.expect
@@ -0,0 +1,32 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+import "dart:_internal" as _in;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+static method main() → dynamic {
+  core::List<dynamic> list = core::_GrowableList::•<dynamic>(0);
+  if(list.{core::Iterable::isNotEmpty}) {
+    let dynamic #t1 = new mai::Class::•() in let dynamic #t2 = _in::unsafeCast<dynamic>(null) as{TypeError,ForDynamic,ForNonNullableByDefault} mai::Enum in throw "Attempt to execute code removed by Dart AOT compiler (TFA)";
+  }
+}
+
+library /*isNonNullableByDefault*/;
+import self as mai;
+import "dart:core" as core;
+
+abstract class Enum extends core::Object {
+}
+class Class extends core::Object {
+  synthetic constructor •() → mai::Class
+    : super core::Object::•()
+    ;
+}
+
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///main_lib.dart:
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.textual_outline.expect b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.textual_outline.expect
new file mode 100644
index 0000000..82be6bb
--- /dev/null
+++ b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.textual_outline.expect
@@ -0,0 +1,3 @@
+import 'main_lib.dart';
+
+main() {}
diff --git a/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..82be6bb
--- /dev/null
+++ b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.textual_outline_modelled.expect
@@ -0,0 +1,3 @@
+import 'main_lib.dart';
+
+main() {}
diff --git a/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.weak.expect b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.weak.expect
new file mode 100644
index 0000000..d3516e5
--- /dev/null
+++ b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.weak.expect
@@ -0,0 +1,49 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+static method main() → dynamic {
+  core::List<dynamic> list = <dynamic>[];
+  if(list.{core::Iterable::isNotEmpty}) {
+    new mai::Class::•().{mai::Class::method}((null as{ForNonNullableByDefault} dynamic) as{TypeError,ForDynamic,ForNonNullableByDefault} mai::Enum);
+  }
+}
+
+library /*isNonNullableByDefault*/;
+import self as mai;
+import "dart:core" as core;
+
+class Enum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<mai::Enum> values = #C4;
+  static const field mai::Enum a = #C3;
+  const constructor •(core::int index, core::String _name) → mai::Enum
+    : mai::Enum::index = index, mai::Enum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{mai::Enum::_name};
+}
+class Class extends core::Object {
+  synthetic constructor •() → mai::Class
+    : super core::Object::•()
+    ;
+  method method(mai::Enum e) → core::int
+    return e.{mai::Enum::index};
+}
+
+constants  {
+  #C1 = 0
+  #C2 = "Enum.a"
+  #C3 = mai::Enum {index:#C1, _name:#C2}
+  #C4 = <mai::Enum*>[#C3]
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///main_lib.dart:
+- Enum. (from org-dartlang-testcase:///main_lib.dart:5:6)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.weak.outline.expect b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.weak.outline.expect
new file mode 100644
index 0000000..5574f01
--- /dev/null
+++ b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.weak.outline.expect
@@ -0,0 +1,42 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+static method main() → dynamic
+  ;
+
+library /*isNonNullableByDefault*/;
+import self as self2;
+import "dart:core" as core;
+
+class Enum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self2::Enum> values = #C4;
+  static const field self2::Enum a = #C3;
+  const constructor •(core::int index, core::String _name) → self2::Enum
+    : self2::Enum::index = index, self2::Enum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{self2::Enum::_name};
+}
+class Class extends core::Object {
+  synthetic constructor •() → self2::Class
+    ;
+  method method(self2::Enum e) → core::int
+    ;
+}
+
+constants  {
+  #C1 = 0
+  #C2 = "Enum.a"
+  #C3 = self2::Enum {index:#C1, _name:#C2}
+  #C4 = <self2::Enum*>[#C3]
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///main_lib.dart:
+- Enum. (from org-dartlang-testcase:///main_lib.dart:5:6)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.weak.transformed.expect b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.weak.transformed.expect
new file mode 100644
index 0000000..040adbc
--- /dev/null
+++ b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main.dart.weak.transformed.expect
@@ -0,0 +1,35 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+import "dart:_internal" as _in;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+static method main() → dynamic {
+  core::List<dynamic> list = core::_GrowableList::•<dynamic>(0);
+  if(list.{core::Iterable::isNotEmpty}) {
+    new mai::Class::•().{mai::Class::method}(_in::unsafeCast<mai::Enum>(_in::unsafeCast<dynamic>(null)));
+  }
+}
+
+library /*isNonNullableByDefault*/;
+import self as mai;
+import "dart:core" as core;
+
+abstract class Enum extends core::Object {
+  abstract get /*isLegacy*/ index() → core::int;
+}
+class Class extends core::Object {
+  synthetic constructor •() → mai::Class
+    : super core::Object::•()
+    ;
+  method method(mai::Enum e) → core::int
+    return e.{mai::Enum::index};
+}
+
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///main_lib.dart:
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main_lib.dart b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main_lib.dart
new file mode 100644
index 0000000..e5600e4
--- /dev/null
+++ b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/main_lib.dart
@@ -0,0 +1,9 @@
+// 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.
+
+enum Enum { a }
+
+class Class {
+  int method(Enum e) => e.index;
+}
diff --git a/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/test.options b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/test.options
new file mode 100644
index 0000000..bfe6dc8
--- /dev/null
+++ b/pkg/front_end/testcases/aot/enum_from_lib_used_as_type/test.options
@@ -0,0 +1 @@
+main_lib.dart
\ No newline at end of file
diff --git a/pkg/front_end/testcases/aot/folder.options b/pkg/front_end/testcases/aot/folder.options
new file mode 100644
index 0000000..a8a8650
--- /dev/null
+++ b/pkg/front_end/testcases/aot/folder.options
@@ -0,0 +1 @@
+--target=aot
\ No newline at end of file
diff --git a/pkg/front_end/testcases/aot/tree_shake/main.dart b/pkg/front_end/testcases/aot/tree_shake/main.dart
new file mode 100644
index 0000000..8958414
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake/main.dart
@@ -0,0 +1,40 @@
+// 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 'main_lib.dart';
+
+enum UnusedEnum { a, b }
+enum UsedEnum {
+  unusedValue,
+  usedValue,
+}
+
+usedMethod(UnusedInterface c) {
+  c.usedInterfaceField = c.usedInterfaceField;
+}
+
+unusedMethod() {}
+
+class UnusedInterface {
+  int? usedInterfaceField;
+
+  UnusedInterface(this.usedInterfaceField);
+}
+
+class UsedClass implements UnusedInterface {
+  int? unusedField;
+  int? usedField;
+  int? usedInterfaceField;
+}
+
+class UnusedClass {}
+
+main() {
+  usedMethod(new UsedClass()..usedField);
+  UsedEnum.usedValue;
+  List<UnusedEnum> list = [];
+  if (list.isNotEmpty) {
+    new ConstClass().method(null as dynamic);
+  }
+}
diff --git a/pkg/front_end/testcases/aot/tree_shake/main.dart.strong.expect b/pkg/front_end/testcases/aot/tree_shake/main.dart.strong.expect
new file mode 100644
index 0000000..57eb8a2
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake/main.dart.strong.expect
@@ -0,0 +1,116 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+class UnusedEnum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self::UnusedEnum> values = #C7;
+  static const field self::UnusedEnum a = #C3;
+  static const field self::UnusedEnum b = #C6;
+  const constructor •(core::int index, core::String _name) → self::UnusedEnum
+    : self::UnusedEnum::index = index, self::UnusedEnum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{self::UnusedEnum::_name};
+}
+class UsedEnum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self::UsedEnum> values = #C12;
+  static const field self::UsedEnum unusedValue = #C9;
+  static const field self::UsedEnum usedValue = #C11;
+  const constructor •(core::int index, core::String _name) → self::UsedEnum
+    : self::UsedEnum::index = index, self::UsedEnum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{self::UsedEnum::_name};
+}
+class UnusedInterface extends core::Object {
+  field core::int? usedInterfaceField;
+  constructor •(core::int? usedInterfaceField) → self::UnusedInterface
+    : self::UnusedInterface::usedInterfaceField = usedInterfaceField, super core::Object::•()
+    ;
+}
+class UsedClass extends core::Object implements self::UnusedInterface {
+  field core::int? unusedField = null;
+  field core::int? usedField = null;
+  field core::int? usedInterfaceField = null;
+  synthetic constructor •() → self::UsedClass
+    : super core::Object::•()
+    ;
+}
+class UnusedClass extends core::Object {
+  synthetic constructor •() → self::UnusedClass
+    : super core::Object::•()
+    ;
+}
+static method usedMethod(self::UnusedInterface c) → dynamic {
+  c.{self::UnusedInterface::usedInterfaceField} = c.{self::UnusedInterface::usedInterfaceField};
+}
+static method unusedMethod() → dynamic {}
+static method main() → dynamic {
+  self::usedMethod(let final self::UsedClass #t1 = new self::UsedClass::•() in block {
+    #t1.{self::UsedClass::usedField};
+  } =>#t1);
+  #C11;
+  core::List<self::UnusedEnum> list = <self::UnusedEnum>[];
+  if(list.{core::Iterable::isNotEmpty}) {
+    new mai::ConstClass::•().{mai::ConstClass::method}((null as{ForNonNullableByDefault} dynamic) as{TypeError,ForDynamic,ForNonNullableByDefault} mai::ConstEnum);
+  }
+}
+
+library /*isNonNullableByDefault*/;
+import self as mai;
+import "dart:core" as core;
+
+class ConstEnum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<mai::ConstEnum> values = #C15;
+  static const field mai::ConstEnum value = #C14;
+  const constructor •(core::int index, core::String _name) → mai::ConstEnum
+    : mai::ConstEnum::index = index, mai::ConstEnum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{mai::ConstEnum::_name};
+}
+class ConstClass extends core::Object {
+  synthetic constructor •() → mai::ConstClass
+    : super core::Object::•()
+    ;
+  method method(mai::ConstEnum e) → core::int
+    return e.{mai::ConstEnum::index};
+}
+
+constants  {
+  #C1 = 0
+  #C2 = "UnusedEnum.a"
+  #C3 = self::UnusedEnum {index:#C1, _name:#C2}
+  #C4 = 1
+  #C5 = "UnusedEnum.b"
+  #C6 = self::UnusedEnum {index:#C4, _name:#C5}
+  #C7 = <self::UnusedEnum>[#C3, #C6]
+  #C8 = "UsedEnum.unusedValue"
+  #C9 = self::UsedEnum {index:#C1, _name:#C8}
+  #C10 = "UsedEnum.usedValue"
+  #C11 = self::UsedEnum {index:#C4, _name:#C10}
+  #C12 = <self::UsedEnum>[#C9, #C11]
+  #C13 = "ConstEnum.value"
+  #C14 = mai::ConstEnum {index:#C1, _name:#C13}
+  #C15 = <mai::ConstEnum>[#C14]
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///main_lib.dart:
+- ConstEnum. (from org-dartlang-testcase:///main_lib.dart:5:6)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
+
+org-dartlang-testcase:///main.dart:
+- UnusedEnum. (from org-dartlang-testcase:///main.dart:7:6)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
+- UsedEnum. (from org-dartlang-testcase:///main.dart:8:6)
diff --git a/pkg/front_end/testcases/aot/tree_shake/main.dart.strong.transformed.expect b/pkg/front_end/testcases/aot/tree_shake/main.dart.strong.transformed.expect
new file mode 100644
index 0000000..23b7ce8
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake/main.dart.strong.transformed.expect
@@ -0,0 +1,66 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+import "dart:_internal" as _in;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+abstract class UnusedEnum extends core::Object {
+}
+class UsedEnum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  method toString() → core::String
+    return this.{self::UsedEnum::_name};
+}
+abstract class UnusedInterface extends core::Object {
+  abstract get /*isLegacy*/ usedInterfaceField() → core::int?;
+  abstract set /*isLegacy*/ usedInterfaceField(core::int? value) → void;
+}
+class UsedClass extends core::Object implements self::UnusedInterface {
+  field core::int? usedField = null;
+  field core::int? usedInterfaceField = null;
+  synthetic constructor •() → self::UsedClass
+    : super core::Object::•()
+    ;
+}
+static method usedMethod(self::UnusedInterface c) → dynamic {
+  c.{self::UnusedInterface::usedInterfaceField} = c.{self::UnusedInterface::usedInterfaceField};
+}
+static method main() → dynamic {
+  self::usedMethod(let final self::UsedClass #t1 = new self::UsedClass::•() in block {
+    #t1.{self::UsedClass::usedField};
+  } =>#t1);
+  #C3;
+  core::List<self::UnusedEnum> list = core::_GrowableList::•<self::UnusedEnum>(0);
+  if(list.{core::Iterable::isNotEmpty}) {
+    let dynamic #t2 = new mai::ConstClass::•() in let dynamic #t3 = _in::unsafeCast<dynamic>(null) as{TypeError,ForDynamic,ForNonNullableByDefault} mai::ConstEnum in throw "Attempt to execute code removed by Dart AOT compiler (TFA)";
+  }
+}
+
+library /*isNonNullableByDefault*/;
+import self as mai;
+import "dart:core" as core;
+
+abstract class ConstEnum extends core::Object {
+}
+class ConstClass extends core::Object {
+  synthetic constructor •() → mai::ConstClass
+    : super core::Object::•()
+    ;
+}
+
+constants  {
+  #C1 = 1
+  #C2 = "UsedEnum.usedValue"
+  #C3 = self::UsedEnum {index:#C1, _name:#C2}
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///main_lib.dart:
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
+
+org-dartlang-testcase:///main.dart:
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/aot/tree_shake/main.dart.textual_outline.expect b/pkg/front_end/testcases/aot/tree_shake/main.dart.textual_outline.expect
new file mode 100644
index 0000000..97f788b
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake/main.dart.textual_outline.expect
@@ -0,0 +1,24 @@
+import 'main_lib.dart';
+
+enum UnusedEnum { a, b }
+enum UsedEnum {
+  unusedValue,
+  usedValue,
+}
+usedMethod(UnusedInterface c) {}
+unusedMethod() {}
+
+class UnusedInterface {
+  int? usedInterfaceField;
+  UnusedInterface(this.usedInterfaceField);
+}
+
+class UsedClass implements UnusedInterface {
+  int? unusedField;
+  int? usedField;
+  int? usedInterfaceField;
+}
+
+class UnusedClass {}
+
+main() {}
diff --git a/pkg/front_end/testcases/aot/tree_shake/main.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/aot/tree_shake/main.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..a10c6f3
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake/main.dart.textual_outline_modelled.expect
@@ -0,0 +1,23 @@
+import 'main_lib.dart';
+
+class UnusedClass {}
+
+class UnusedInterface {
+  UnusedInterface(this.usedInterfaceField);
+  int? usedInterfaceField;
+}
+
+class UsedClass implements UnusedInterface {
+  int? unusedField;
+  int? usedField;
+  int? usedInterfaceField;
+}
+
+enum UnusedEnum { a, b }
+enum UsedEnum {
+  unusedValue,
+  usedValue,
+}
+main() {}
+unusedMethod() {}
+usedMethod(UnusedInterface c) {}
diff --git a/pkg/front_end/testcases/aot/tree_shake/main.dart.weak.expect b/pkg/front_end/testcases/aot/tree_shake/main.dart.weak.expect
new file mode 100644
index 0000000..55eeff3
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake/main.dart.weak.expect
@@ -0,0 +1,116 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+class UnusedEnum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self::UnusedEnum> values = #C7;
+  static const field self::UnusedEnum a = #C3;
+  static const field self::UnusedEnum b = #C6;
+  const constructor •(core::int index, core::String _name) → self::UnusedEnum
+    : self::UnusedEnum::index = index, self::UnusedEnum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{self::UnusedEnum::_name};
+}
+class UsedEnum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self::UsedEnum> values = #C12;
+  static const field self::UsedEnum unusedValue = #C9;
+  static const field self::UsedEnum usedValue = #C11;
+  const constructor •(core::int index, core::String _name) → self::UsedEnum
+    : self::UsedEnum::index = index, self::UsedEnum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{self::UsedEnum::_name};
+}
+class UnusedInterface extends core::Object {
+  field core::int? usedInterfaceField;
+  constructor •(core::int? usedInterfaceField) → self::UnusedInterface
+    : self::UnusedInterface::usedInterfaceField = usedInterfaceField, super core::Object::•()
+    ;
+}
+class UsedClass extends core::Object implements self::UnusedInterface {
+  field core::int? unusedField = null;
+  field core::int? usedField = null;
+  field core::int? usedInterfaceField = null;
+  synthetic constructor •() → self::UsedClass
+    : super core::Object::•()
+    ;
+}
+class UnusedClass extends core::Object {
+  synthetic constructor •() → self::UnusedClass
+    : super core::Object::•()
+    ;
+}
+static method usedMethod(self::UnusedInterface c) → dynamic {
+  c.{self::UnusedInterface::usedInterfaceField} = c.{self::UnusedInterface::usedInterfaceField};
+}
+static method unusedMethod() → dynamic {}
+static method main() → dynamic {
+  self::usedMethod(let final self::UsedClass #t1 = new self::UsedClass::•() in block {
+    #t1.{self::UsedClass::usedField};
+  } =>#t1);
+  #C11;
+  core::List<self::UnusedEnum> list = <self::UnusedEnum>[];
+  if(list.{core::Iterable::isNotEmpty}) {
+    new mai::ConstClass::•().{mai::ConstClass::method}((null as{ForNonNullableByDefault} dynamic) as{TypeError,ForDynamic,ForNonNullableByDefault} mai::ConstEnum);
+  }
+}
+
+library /*isNonNullableByDefault*/;
+import self as mai;
+import "dart:core" as core;
+
+class ConstEnum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<mai::ConstEnum> values = #C15;
+  static const field mai::ConstEnum value = #C14;
+  const constructor •(core::int index, core::String _name) → mai::ConstEnum
+    : mai::ConstEnum::index = index, mai::ConstEnum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{mai::ConstEnum::_name};
+}
+class ConstClass extends core::Object {
+  synthetic constructor •() → mai::ConstClass
+    : super core::Object::•()
+    ;
+  method method(mai::ConstEnum e) → core::int
+    return e.{mai::ConstEnum::index};
+}
+
+constants  {
+  #C1 = 0
+  #C2 = "UnusedEnum.a"
+  #C3 = self::UnusedEnum {index:#C1, _name:#C2}
+  #C4 = 1
+  #C5 = "UnusedEnum.b"
+  #C6 = self::UnusedEnum {index:#C4, _name:#C5}
+  #C7 = <self::UnusedEnum*>[#C3, #C6]
+  #C8 = "UsedEnum.unusedValue"
+  #C9 = self::UsedEnum {index:#C1, _name:#C8}
+  #C10 = "UsedEnum.usedValue"
+  #C11 = self::UsedEnum {index:#C4, _name:#C10}
+  #C12 = <self::UsedEnum*>[#C9, #C11]
+  #C13 = "ConstEnum.value"
+  #C14 = mai::ConstEnum {index:#C1, _name:#C13}
+  #C15 = <mai::ConstEnum*>[#C14]
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///main_lib.dart:
+- ConstEnum. (from org-dartlang-testcase:///main_lib.dart:5:6)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
+
+org-dartlang-testcase:///main.dart:
+- UnusedEnum. (from org-dartlang-testcase:///main.dart:7:6)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
+- UsedEnum. (from org-dartlang-testcase:///main.dart:8:6)
diff --git a/pkg/front_end/testcases/aot/tree_shake/main.dart.weak.outline.expect b/pkg/front_end/testcases/aot/tree_shake/main.dart.weak.outline.expect
new file mode 100644
index 0000000..43c42d9
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake/main.dart.weak.outline.expect
@@ -0,0 +1,96 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+class UnusedEnum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self::UnusedEnum> values = const <self::UnusedEnum>[self::UnusedEnum::a, self::UnusedEnum::b];
+  static const field self::UnusedEnum a = const self::UnusedEnum::•(0, "UnusedEnum.a");
+  static const field self::UnusedEnum b = const self::UnusedEnum::•(1, "UnusedEnum.b");
+  const constructor •(core::int index, core::String _name) → self::UnusedEnum
+    : self::UnusedEnum::index = index, self::UnusedEnum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{self::UnusedEnum::_name};
+}
+class UsedEnum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self::UsedEnum> values = const <self::UsedEnum>[self::UsedEnum::unusedValue, self::UsedEnum::usedValue];
+  static const field self::UsedEnum unusedValue = const self::UsedEnum::•(0, "UsedEnum.unusedValue");
+  static const field self::UsedEnum usedValue = const self::UsedEnum::•(1, "UsedEnum.usedValue");
+  const constructor •(core::int index, core::String _name) → self::UsedEnum
+    : self::UsedEnum::index = index, self::UsedEnum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{self::UsedEnum::_name};
+}
+class UnusedInterface extends core::Object {
+  field core::int? usedInterfaceField;
+  constructor •(core::int? usedInterfaceField) → self::UnusedInterface
+    ;
+}
+class UsedClass extends core::Object implements self::UnusedInterface {
+  field core::int? unusedField;
+  field core::int? usedField;
+  field core::int? usedInterfaceField;
+  synthetic constructor •() → self::UsedClass
+    ;
+}
+class UnusedClass extends core::Object {
+  synthetic constructor •() → self::UnusedClass
+    ;
+}
+static method usedMethod(self::UnusedInterface c) → dynamic
+  ;
+static method unusedMethod() → dynamic
+  ;
+static method main() → dynamic
+  ;
+
+library /*isNonNullableByDefault*/;
+import self as self2;
+import "dart:core" as core;
+
+class ConstEnum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  static const field core::List<self2::ConstEnum> values = #C4;
+  static const field self2::ConstEnum value = #C3;
+  const constructor •(core::int index, core::String _name) → self2::ConstEnum
+    : self2::ConstEnum::index = index, self2::ConstEnum::_name = _name, super core::Object::•()
+    ;
+  method toString() → core::String
+    return this.{self2::ConstEnum::_name};
+}
+class ConstClass extends core::Object {
+  synthetic constructor •() → self2::ConstClass
+    ;
+  method method(self2::ConstEnum e) → core::int
+    ;
+}
+
+constants  {
+  #C1 = 0
+  #C2 = "ConstEnum.value"
+  #C3 = self2::ConstEnum {index:#C1, _name:#C2}
+  #C4 = <self2::ConstEnum*>[#C3]
+}
+
+Extra constant evaluation status:
+Evaluated: ListLiteral @ org-dartlang-testcase:///main.dart:7:6 -> ListConstant(const <UnusedEnum*>[const UnusedEnum{UnusedEnum.index: 0, UnusedEnum._name: "UnusedEnum.a"}, const UnusedEnum{UnusedEnum.index: 1, UnusedEnum._name: "UnusedEnum.b"}])
+Evaluated: ConstructorInvocation @ org-dartlang-testcase:///main.dart:7:19 -> InstanceConstant(const UnusedEnum{UnusedEnum.index: 0, UnusedEnum._name: "UnusedEnum.a"})
+Evaluated: ConstructorInvocation @ org-dartlang-testcase:///main.dart:7:22 -> InstanceConstant(const UnusedEnum{UnusedEnum.index: 1, UnusedEnum._name: "UnusedEnum.b"})
+Evaluated: ListLiteral @ org-dartlang-testcase:///main.dart:8:6 -> ListConstant(const <UsedEnum*>[const UsedEnum{UsedEnum.index: 0, UsedEnum._name: "UsedEnum.unusedValue"}, const UsedEnum{UsedEnum.index: 1, UsedEnum._name: "UsedEnum.usedValue"}])
+Evaluated: ConstructorInvocation @ org-dartlang-testcase:///main.dart:9:3 -> InstanceConstant(const UsedEnum{UsedEnum.index: 0, UsedEnum._name: "UsedEnum.unusedValue"})
+Evaluated: ConstructorInvocation @ org-dartlang-testcase:///main.dart:10:3 -> InstanceConstant(const UsedEnum{UsedEnum.index: 1, UsedEnum._name: "UsedEnum.usedValue"})
+Extra constant evaluation: evaluated: 18, effectively constant: 6
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///main_lib.dart:
+- ConstEnum. (from org-dartlang-testcase:///main_lib.dart:5:6)
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/aot/tree_shake/main.dart.weak.transformed.expect b/pkg/front_end/testcases/aot/tree_shake/main.dart.weak.transformed.expect
new file mode 100644
index 0000000..80c0d6a
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake/main.dart.weak.transformed.expect
@@ -0,0 +1,69 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+import "dart:_internal" as _in;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+abstract class UnusedEnum extends core::Object {
+}
+class UsedEnum extends core::Object /*isEnum*/  {
+  final field core::int index;
+  final field core::String _name;
+  method toString() → core::String
+    return this.{self::UsedEnum::_name};
+}
+abstract class UnusedInterface extends core::Object {
+  abstract get /*isLegacy*/ usedInterfaceField() → core::int?;
+  abstract set /*isLegacy*/ usedInterfaceField(core::int? value) → void;
+}
+class UsedClass extends core::Object implements self::UnusedInterface {
+  field core::int? usedField = null;
+  field core::int? usedInterfaceField = null;
+  synthetic constructor •() → self::UsedClass
+    : super core::Object::•()
+    ;
+}
+static method usedMethod(self::UnusedInterface c) → dynamic {
+  c.{self::UnusedInterface::usedInterfaceField} = c.{self::UnusedInterface::usedInterfaceField};
+}
+static method main() → dynamic {
+  self::usedMethod(let final self::UsedClass #t1 = new self::UsedClass::•() in block {
+    #t1.{self::UsedClass::usedField};
+  } =>#t1);
+  #C3;
+  core::List<self::UnusedEnum> list = core::_GrowableList::•<self::UnusedEnum>(0);
+  if(list.{core::Iterable::isNotEmpty}) {
+    new mai::ConstClass::•().{mai::ConstClass::method}(_in::unsafeCast<mai::ConstEnum>(_in::unsafeCast<dynamic>(null)));
+  }
+}
+
+library /*isNonNullableByDefault*/;
+import self as mai;
+import "dart:core" as core;
+
+abstract class ConstEnum extends core::Object {
+  abstract get /*isLegacy*/ index() → core::int;
+}
+class ConstClass extends core::Object {
+  synthetic constructor •() → mai::ConstClass
+    : super core::Object::•()
+    ;
+  method method(mai::ConstEnum e) → core::int
+    return e.{mai::ConstEnum::index};
+}
+
+constants  {
+  #C1 = 1
+  #C2 = "UsedEnum.usedValue"
+  #C3 = self::UsedEnum {index:#C1, _name:#C2}
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///main_lib.dart:
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
+
+org-dartlang-testcase:///main.dart:
+- Object. (from org-dartlang-sdk:///sdk/lib/core/object.dart:25:9)
diff --git a/pkg/front_end/testcases/aot/tree_shake/main_lib.dart b/pkg/front_end/testcases/aot/tree_shake/main_lib.dart
new file mode 100644
index 0000000..bc873dc
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake/main_lib.dart
@@ -0,0 +1,9 @@
+// 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.
+
+enum ConstEnum { value }
+
+class ConstClass {
+  int method(ConstEnum e) => e.index;
+}
diff --git a/pkg/front_end/testcases/aot/tree_shake/test.options b/pkg/front_end/testcases/aot/tree_shake/test.options
new file mode 100644
index 0000000..bfe6dc8
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake/test.options
@@ -0,0 +1 @@
+main_lib.dart
\ No newline at end of file
diff --git a/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart
new file mode 100644
index 0000000..53ab22e
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart
@@ -0,0 +1,20 @@
+// 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 'main_lib.dart';
+
+class Class implements Interface {
+  int? field1;
+  int? field2;
+  int? field3;
+}
+
+void method(Interface i) {
+  i.field2 = i.field1;
+  i.field3 = i.field3;
+}
+
+main() {
+  method(new Class());
+}
diff --git a/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.strong.expect b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.strong.expect
new file mode 100644
index 0000000..fce2045
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.strong.expect
@@ -0,0 +1,35 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+class Class extends core::Object implements mai::Interface {
+  field core::int? field1 = null;
+  field core::int? field2 = null;
+  field core::int? field3 = null;
+  synthetic constructor •() → self::Class
+    : super core::Object::•()
+    ;
+}
+static method method(mai::Interface i) → void {
+  i.{mai::Interface::field2} = i.{mai::Interface::field1};
+  i.{mai::Interface::field3} = i.{mai::Interface::field3};
+}
+static method main() → dynamic {
+  self::method(new self::Class::•());
+}
+
+library /*isNonNullableByDefault*/;
+import self as mai;
+import "dart:core" as core;
+
+class Interface extends core::Object {
+  field core::int? field1 = null;
+  field core::int? field2 = null;
+  field core::int? field3 = null;
+  synthetic constructor •() → mai::Interface
+    : super core::Object::•()
+    ;
+}
diff --git a/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.strong.transformed.expect b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.strong.transformed.expect
new file mode 100644
index 0000000..9a18bdf
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.strong.transformed.expect
@@ -0,0 +1,33 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+class Class extends core::Object implements mai::Interface {
+  field core::int? field1 = null;
+  field core::int? field3 = null;
+  synthetic constructor •() → self::Class
+    : super core::Object::•()
+    ;
+  set /*isLegacy*/ field2(core::int? value) → void;
+}
+static method method(mai::Interface i) → void {
+  i.{mai::Interface::field2} = i.{mai::Interface::field1};
+  i.{mai::Interface::field3} = i.{mai::Interface::field3};
+}
+static method main() → dynamic {
+  self::method(new self::Class::•());
+}
+
+library /*isNonNullableByDefault*/;
+import self as mai;
+import "dart:core" as core;
+
+abstract class Interface extends core::Object {
+  abstract get /*isLegacy*/ field1() → core::int?;
+  abstract set /*isLegacy*/ field2(core::int? value) → void;
+  abstract get /*isLegacy*/ field3() → core::int?;
+  abstract set /*isLegacy*/ field3(core::int? value) → void;
+}
diff --git a/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.textual_outline.expect b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.textual_outline.expect
new file mode 100644
index 0000000..433a1aa
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.textual_outline.expect
@@ -0,0 +1,10 @@
+import 'main_lib.dart';
+
+class Class implements Interface {
+  int? field1;
+  int? field2;
+  int? field3;
+}
+
+void method(Interface i) {}
+main() {}
diff --git a/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..60b9b0d
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.textual_outline_modelled.expect
@@ -0,0 +1,10 @@
+import 'main_lib.dart';
+
+class Class implements Interface {
+  int? field1;
+  int? field2;
+  int? field3;
+}
+
+main() {}
+void method(Interface i) {}
diff --git a/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.weak.expect b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.weak.expect
new file mode 100644
index 0000000..fce2045
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.weak.expect
@@ -0,0 +1,35 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+class Class extends core::Object implements mai::Interface {
+  field core::int? field1 = null;
+  field core::int? field2 = null;
+  field core::int? field3 = null;
+  synthetic constructor •() → self::Class
+    : super core::Object::•()
+    ;
+}
+static method method(mai::Interface i) → void {
+  i.{mai::Interface::field2} = i.{mai::Interface::field1};
+  i.{mai::Interface::field3} = i.{mai::Interface::field3};
+}
+static method main() → dynamic {
+  self::method(new self::Class::•());
+}
+
+library /*isNonNullableByDefault*/;
+import self as mai;
+import "dart:core" as core;
+
+class Interface extends core::Object {
+  field core::int? field1 = null;
+  field core::int? field2 = null;
+  field core::int? field3 = null;
+  synthetic constructor •() → mai::Interface
+    : super core::Object::•()
+    ;
+}
diff --git a/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.weak.outline.expect b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.weak.outline.expect
new file mode 100644
index 0000000..7018a21
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.weak.outline.expect
@@ -0,0 +1,30 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+class Class extends core::Object implements mai::Interface {
+  field core::int? field1;
+  field core::int? field2;
+  field core::int? field3;
+  synthetic constructor •() → self::Class
+    ;
+}
+static method method(mai::Interface i) → void
+  ;
+static method main() → dynamic
+  ;
+
+library /*isNonNullableByDefault*/;
+import self as mai;
+import "dart:core" as core;
+
+class Interface extends core::Object {
+  field core::int? field1;
+  field core::int? field2;
+  field core::int? field3;
+  synthetic constructor •() → mai::Interface
+    ;
+}
diff --git a/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.weak.transformed.expect b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.weak.transformed.expect
new file mode 100644
index 0000000..9a18bdf
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main.dart.weak.transformed.expect
@@ -0,0 +1,33 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "main_lib.dart" as mai;
+
+import "org-dartlang-testcase:///main_lib.dart";
+
+class Class extends core::Object implements mai::Interface {
+  field core::int? field1 = null;
+  field core::int? field3 = null;
+  synthetic constructor •() → self::Class
+    : super core::Object::•()
+    ;
+  set /*isLegacy*/ field2(core::int? value) → void;
+}
+static method method(mai::Interface i) → void {
+  i.{mai::Interface::field2} = i.{mai::Interface::field1};
+  i.{mai::Interface::field3} = i.{mai::Interface::field3};
+}
+static method main() → dynamic {
+  self::method(new self::Class::•());
+}
+
+library /*isNonNullableByDefault*/;
+import self as mai;
+import "dart:core" as core;
+
+abstract class Interface extends core::Object {
+  abstract get /*isLegacy*/ field1() → core::int?;
+  abstract set /*isLegacy*/ field2(core::int? value) → void;
+  abstract get /*isLegacy*/ field3() → core::int?;
+  abstract set /*isLegacy*/ field3(core::int? value) → void;
+}
diff --git a/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main_lib.dart b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main_lib.dart
new file mode 100644
index 0000000..190ab1a
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/main_lib.dart
@@ -0,0 +1,9 @@
+// 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.
+
+class Interface {
+  int? field1;
+  int? field2;
+  int? field3;
+}
diff --git a/pkg/front_end/testcases/aot/tree_shake_field_from_lib/test.options b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/test.options
new file mode 100644
index 0000000..bfe6dc8
--- /dev/null
+++ b/pkg/front_end/testcases/aot/tree_shake_field_from_lib/test.options
@@ -0,0 +1 @@
+main_lib.dart
\ No newline at end of file
diff --git a/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart
new file mode 100644
index 0000000..114ac6f
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart
@@ -0,0 +1,20 @@
+// 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.
+
+class A {
+  int get foo => 42;
+}
+
+extension E on A {
+  double get bar => 3.14;
+}
+
+test(A a, E e) {
+  a.foo; // Ok.
+  a.bar; // Ok.
+  e.foo; // Error.
+  e.bar; // Ok.
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.strong.expect b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.strong.expect
new file mode 100644
index 0000000..8c6d2ea
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.strong.expect
@@ -0,0 +1,34 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/simple_getter_resolution.dart:16:5: Error: The getter 'foo' isn't defined for the extension 'E'.
+// Try correcting the name to the name of an existing getter, or defining a getter or field named 'foo'.
+//   e.foo; // Error.
+//     ^^^
+//
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+  get foo() → core::int
+    return 42;
+}
+extension E on self::A {
+  get bar = self::E|get#bar;
+}
+static method E|get#bar(lowered final self::A #this) → core::double
+  return 3.14;
+static method test(self::A a, self::E e) → dynamic {
+  a.{self::A::foo};
+  self::E|get#bar(a);
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_getter_resolution.dart:16:5: Error: The getter 'foo' isn't defined for the extension 'E'.
+Try correcting the name to the name of an existing getter, or defining a getter or field named 'foo'.
+  e.foo; // Error.
+    ^^^";
+  self::E|get#bar(e);
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.strong.transformed.expect b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.strong.transformed.expect
new file mode 100644
index 0000000..8c6d2ea
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.strong.transformed.expect
@@ -0,0 +1,34 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/simple_getter_resolution.dart:16:5: Error: The getter 'foo' isn't defined for the extension 'E'.
+// Try correcting the name to the name of an existing getter, or defining a getter or field named 'foo'.
+//   e.foo; // Error.
+//     ^^^
+//
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+  get foo() → core::int
+    return 42;
+}
+extension E on self::A {
+  get bar = self::E|get#bar;
+}
+static method E|get#bar(lowered final self::A #this) → core::double
+  return 3.14;
+static method test(self::A a, self::E e) → dynamic {
+  a.{self::A::foo};
+  self::E|get#bar(a);
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_getter_resolution.dart:16:5: Error: The getter 'foo' isn't defined for the extension 'E'.
+Try correcting the name to the name of an existing getter, or defining a getter or field named 'foo'.
+  e.foo; // Error.
+    ^^^";
+  self::E|get#bar(e);
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.textual_outline.expect b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.textual_outline.expect
new file mode 100644
index 0000000..587163f
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.textual_outline.expect
@@ -0,0 +1,10 @@
+class A {
+  int get foo => 42;
+}
+
+extension E on A {
+  double get bar => 3.14;
+}
+
+test(A a, E e) {}
+main() {}
diff --git a/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..8d51c64
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.textual_outline_modelled.expect
@@ -0,0 +1,10 @@
+class A {
+  int get foo => 42;
+}
+
+extension E on A {
+  double get bar => 3.14;
+}
+
+main() {}
+test(A a, E e) {}
diff --git a/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.weak.expect b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.weak.expect
new file mode 100644
index 0000000..8c6d2ea
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.weak.expect
@@ -0,0 +1,34 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/simple_getter_resolution.dart:16:5: Error: The getter 'foo' isn't defined for the extension 'E'.
+// Try correcting the name to the name of an existing getter, or defining a getter or field named 'foo'.
+//   e.foo; // Error.
+//     ^^^
+//
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+  get foo() → core::int
+    return 42;
+}
+extension E on self::A {
+  get bar = self::E|get#bar;
+}
+static method E|get#bar(lowered final self::A #this) → core::double
+  return 3.14;
+static method test(self::A a, self::E e) → dynamic {
+  a.{self::A::foo};
+  self::E|get#bar(a);
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_getter_resolution.dart:16:5: Error: The getter 'foo' isn't defined for the extension 'E'.
+Try correcting the name to the name of an existing getter, or defining a getter or field named 'foo'.
+  e.foo; // Error.
+    ^^^";
+  self::E|get#bar(e);
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.weak.outline.expect b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.weak.outline.expect
new file mode 100644
index 0000000..08cef61
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.weak.outline.expect
@@ -0,0 +1,19 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    ;
+  get foo() → core::int
+    ;
+}
+extension E on self::A {
+  get bar = self::E|get#bar;
+}
+static method E|get#bar(lowered final self::A #this) → core::double
+  ;
+static method test(self::A a, self::E e) → dynamic
+  ;
+static method main() → dynamic
+  ;
diff --git a/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.weak.transformed.expect b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.weak.transformed.expect
new file mode 100644
index 0000000..8c6d2ea
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_getter_resolution.dart.weak.transformed.expect
@@ -0,0 +1,34 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/simple_getter_resolution.dart:16:5: Error: The getter 'foo' isn't defined for the extension 'E'.
+// Try correcting the name to the name of an existing getter, or defining a getter or field named 'foo'.
+//   e.foo; // Error.
+//     ^^^
+//
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+  get foo() → core::int
+    return 42;
+}
+extension E on self::A {
+  get bar = self::E|get#bar;
+}
+static method E|get#bar(lowered final self::A #this) → core::double
+  return 3.14;
+static method test(self::A a, self::E e) → dynamic {
+  a.{self::A::foo};
+  self::E|get#bar(a);
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_getter_resolution.dart:16:5: Error: The getter 'foo' isn't defined for the extension 'E'.
+Try correcting the name to the name of an existing getter, or defining a getter or field named 'foo'.
+  e.foo; // Error.
+    ^^^";
+  self::E|get#bar(e);
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart
new file mode 100644
index 0000000..7f1f10b
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart
@@ -0,0 +1,30 @@
+// 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.
+
+class A {
+  dynamic operator*(dynamic other) => 42;
+  dynamic operator[](int index) => 42;
+  void operator[]=(int index, dynamic value) {}
+  dynamic operator-() => 42;
+}
+
+extension E on A {
+  dynamic operator+(dynamic other) => 42;
+}
+
+test(A a, E e) {
+  a * "foobar"; // Ok.
+  a[0]; // Ok.
+  a[0] = "foobar"; // Ok.
+  -a; // Ok.
+  a + "foobar"; // Ok.
+
+  e * "foobar"; // Error.
+  e[0]; // Error.
+  e[0] = "foobar"; // Error.
+  -e; // Error.
+  e + "foobar"; // Ok.
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.strong.expect b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.strong.expect
new file mode 100644
index 0000000..80d58c8
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.strong.expect
@@ -0,0 +1,69 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:23:5: Error: The operator '*' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a '*' operator.
+//   e * "foobar"; // Error.
+//     ^
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:24:4: Error: The operator '[]' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a '[]' operator.
+//   e[0]; // Error.
+//    ^
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:25:4: Error: The operator '[]=' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a '[]=' operator.
+//   e[0] = "foobar"; // Error.
+//    ^
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:26:3: Error: The operator 'unary-' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a 'unary-' operator.
+//   -e; // Error.
+//   ^
+//
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+  operator *(dynamic other) → dynamic
+    return 42;
+  operator [](core::int index) → dynamic
+    return 42;
+  operator []=(core::int index, dynamic value) → void {}
+  operator unary-() → dynamic
+    return 42;
+}
+extension E on self::A {
+  operator + = self::E|+;
+}
+static method E|+(lowered final self::A #this, dynamic other) → dynamic
+  return 42;
+static method test(self::A a, self::E e) → dynamic {
+  a.{self::A::*}("foobar");
+  a.{self::A::[]}(0);
+  a.{self::A::[]=}(0, "foobar");
+  a.{self::A::unary-}();
+  self::E|+(a, "foobar");
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:23:5: Error: The operator '*' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a '*' operator.
+  e * \"foobar\"; // Error.
+    ^";
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:24:4: Error: The operator '[]' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a '[]' operator.
+  e[0]; // Error.
+   ^";
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:25:4: Error: The operator '[]=' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a '[]=' operator.
+  e[0] = \"foobar\"; // Error.
+   ^";
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:26:3: Error: The operator 'unary-' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a 'unary-' operator.
+  -e; // Error.
+  ^";
+  self::E|+(e, "foobar");
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.strong.transformed.expect b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.strong.transformed.expect
new file mode 100644
index 0000000..80d58c8
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.strong.transformed.expect
@@ -0,0 +1,69 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:23:5: Error: The operator '*' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a '*' operator.
+//   e * "foobar"; // Error.
+//     ^
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:24:4: Error: The operator '[]' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a '[]' operator.
+//   e[0]; // Error.
+//    ^
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:25:4: Error: The operator '[]=' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a '[]=' operator.
+//   e[0] = "foobar"; // Error.
+//    ^
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:26:3: Error: The operator 'unary-' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a 'unary-' operator.
+//   -e; // Error.
+//   ^
+//
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+  operator *(dynamic other) → dynamic
+    return 42;
+  operator [](core::int index) → dynamic
+    return 42;
+  operator []=(core::int index, dynamic value) → void {}
+  operator unary-() → dynamic
+    return 42;
+}
+extension E on self::A {
+  operator + = self::E|+;
+}
+static method E|+(lowered final self::A #this, dynamic other) → dynamic
+  return 42;
+static method test(self::A a, self::E e) → dynamic {
+  a.{self::A::*}("foobar");
+  a.{self::A::[]}(0);
+  a.{self::A::[]=}(0, "foobar");
+  a.{self::A::unary-}();
+  self::E|+(a, "foobar");
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:23:5: Error: The operator '*' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a '*' operator.
+  e * \"foobar\"; // Error.
+    ^";
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:24:4: Error: The operator '[]' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a '[]' operator.
+  e[0]; // Error.
+   ^";
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:25:4: Error: The operator '[]=' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a '[]=' operator.
+  e[0] = \"foobar\"; // Error.
+   ^";
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:26:3: Error: The operator 'unary-' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a 'unary-' operator.
+  -e; // Error.
+  ^";
+  self::E|+(e, "foobar");
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.textual_outline.expect b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.textual_outline.expect
new file mode 100644
index 0000000..b773643
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.textual_outline.expect
@@ -0,0 +1,13 @@
+class A {
+  dynamic operator *(dynamic other) => 42;
+  dynamic operator [](int index) => 42;
+  void operator []=(int index, dynamic value) {}
+  dynamic operator -() => 42;
+}
+
+extension E on A {
+  dynamic operator +(dynamic other) => 42;
+}
+
+test(A a, E e) {}
+main() {}
diff --git a/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..3cbb6bf
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.textual_outline_modelled.expect
@@ -0,0 +1,13 @@
+class A {
+  dynamic operator *(dynamic other) => 42;
+  dynamic operator -() => 42;
+  dynamic operator [](int index) => 42;
+  void operator []=(int index, dynamic value) {}
+}
+
+extension E on A {
+  dynamic operator +(dynamic other) => 42;
+}
+
+main() {}
+test(A a, E e) {}
diff --git a/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.weak.expect b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.weak.expect
new file mode 100644
index 0000000..80d58c8
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.weak.expect
@@ -0,0 +1,69 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:23:5: Error: The operator '*' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a '*' operator.
+//   e * "foobar"; // Error.
+//     ^
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:24:4: Error: The operator '[]' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a '[]' operator.
+//   e[0]; // Error.
+//    ^
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:25:4: Error: The operator '[]=' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a '[]=' operator.
+//   e[0] = "foobar"; // Error.
+//    ^
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:26:3: Error: The operator 'unary-' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a 'unary-' operator.
+//   -e; // Error.
+//   ^
+//
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+  operator *(dynamic other) → dynamic
+    return 42;
+  operator [](core::int index) → dynamic
+    return 42;
+  operator []=(core::int index, dynamic value) → void {}
+  operator unary-() → dynamic
+    return 42;
+}
+extension E on self::A {
+  operator + = self::E|+;
+}
+static method E|+(lowered final self::A #this, dynamic other) → dynamic
+  return 42;
+static method test(self::A a, self::E e) → dynamic {
+  a.{self::A::*}("foobar");
+  a.{self::A::[]}(0);
+  a.{self::A::[]=}(0, "foobar");
+  a.{self::A::unary-}();
+  self::E|+(a, "foobar");
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:23:5: Error: The operator '*' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a '*' operator.
+  e * \"foobar\"; // Error.
+    ^";
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:24:4: Error: The operator '[]' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a '[]' operator.
+  e[0]; // Error.
+   ^";
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:25:4: Error: The operator '[]=' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a '[]=' operator.
+  e[0] = \"foobar\"; // Error.
+   ^";
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:26:3: Error: The operator 'unary-' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a 'unary-' operator.
+  -e; // Error.
+  ^";
+  self::E|+(e, "foobar");
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.weak.outline.expect b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.weak.outline.expect
new file mode 100644
index 0000000..af29517
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.weak.outline.expect
@@ -0,0 +1,25 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    ;
+  operator *(dynamic other) → dynamic
+    ;
+  operator [](core::int index) → dynamic
+    ;
+  operator []=(core::int index, dynamic value) → void
+    ;
+  operator unary-() → dynamic
+    ;
+}
+extension E on self::A {
+  operator + = self::E|+;
+}
+static method E|+(lowered final self::A #this, dynamic other) → dynamic
+  ;
+static method test(self::A a, self::E e) → dynamic
+  ;
+static method main() → dynamic
+  ;
diff --git a/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.weak.transformed.expect b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.weak.transformed.expect
new file mode 100644
index 0000000..80d58c8
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_operator_resolution.dart.weak.transformed.expect
@@ -0,0 +1,69 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:23:5: Error: The operator '*' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a '*' operator.
+//   e * "foobar"; // Error.
+//     ^
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:24:4: Error: The operator '[]' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a '[]' operator.
+//   e[0]; // Error.
+//    ^
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:25:4: Error: The operator '[]=' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a '[]=' operator.
+//   e[0] = "foobar"; // Error.
+//    ^
+//
+// pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:26:3: Error: The operator 'unary-' isn't defined for the extension 'E'.
+// Try correcting the operator to an existing operator, or defining a 'unary-' operator.
+//   -e; // Error.
+//   ^
+//
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+  operator *(dynamic other) → dynamic
+    return 42;
+  operator [](core::int index) → dynamic
+    return 42;
+  operator []=(core::int index, dynamic value) → void {}
+  operator unary-() → dynamic
+    return 42;
+}
+extension E on self::A {
+  operator + = self::E|+;
+}
+static method E|+(lowered final self::A #this, dynamic other) → dynamic
+  return 42;
+static method test(self::A a, self::E e) → dynamic {
+  a.{self::A::*}("foobar");
+  a.{self::A::[]}(0);
+  a.{self::A::[]=}(0, "foobar");
+  a.{self::A::unary-}();
+  self::E|+(a, "foobar");
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:23:5: Error: The operator '*' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a '*' operator.
+  e * \"foobar\"; // Error.
+    ^";
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:24:4: Error: The operator '[]' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a '[]' operator.
+  e[0]; // Error.
+   ^";
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:25:4: Error: The operator '[]=' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a '[]=' operator.
+  e[0] = \"foobar\"; // Error.
+   ^";
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_operator_resolution.dart:26:3: Error: The operator 'unary-' isn't defined for the extension 'E'.
+Try correcting the operator to an existing operator, or defining a 'unary-' operator.
+  -e; // Error.
+  ^";
+  self::E|+(e, "foobar");
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart
new file mode 100644
index 0000000..20f43d6
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart
@@ -0,0 +1,20 @@
+// 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.
+
+class A {
+  void set foo(int value) {}
+}
+
+extension E on A {
+  void set bar(int value) {}
+}
+
+test(A a, E e) {
+  a.foo = 42; // Ok.
+  a.bar = 42; // Ok.
+  e.foo = 42; // Error.
+  e.bar = 42; // Ok.
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.strong.expect b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.strong.expect
new file mode 100644
index 0000000..c7f4354
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.strong.expect
@@ -0,0 +1,32 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/simple_setter_resolution.dart:16:5: Error: The setter 'foo' isn't defined for the extension 'E'.
+// Try correcting the name to the name of an existing setter, or defining a setter or field named 'foo'.
+//   e.foo = 42; // Error.
+//     ^^^
+//
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+  set foo(core::int value) → void {}
+}
+extension E on self::A {
+  set bar = self::E|set#bar;
+}
+static method E|set#bar(lowered final self::A #this, core::int value) → void {}
+static method test(self::A a, self::E e) → dynamic {
+  a.{self::A::foo} = 42;
+  self::E|set#bar(a, 42);
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_setter_resolution.dart:16:5: Error: The setter 'foo' isn't defined for the extension 'E'.
+Try correcting the name to the name of an existing setter, or defining a setter or field named 'foo'.
+  e.foo = 42; // Error.
+    ^^^";
+  self::E|set#bar(e, 42);
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.strong.transformed.expect b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.strong.transformed.expect
new file mode 100644
index 0000000..c7f4354
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.strong.transformed.expect
@@ -0,0 +1,32 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/simple_setter_resolution.dart:16:5: Error: The setter 'foo' isn't defined for the extension 'E'.
+// Try correcting the name to the name of an existing setter, or defining a setter or field named 'foo'.
+//   e.foo = 42; // Error.
+//     ^^^
+//
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+  set foo(core::int value) → void {}
+}
+extension E on self::A {
+  set bar = self::E|set#bar;
+}
+static method E|set#bar(lowered final self::A #this, core::int value) → void {}
+static method test(self::A a, self::E e) → dynamic {
+  a.{self::A::foo} = 42;
+  self::E|set#bar(a, 42);
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_setter_resolution.dart:16:5: Error: The setter 'foo' isn't defined for the extension 'E'.
+Try correcting the name to the name of an existing setter, or defining a setter or field named 'foo'.
+  e.foo = 42; // Error.
+    ^^^";
+  self::E|set#bar(e, 42);
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.textual_outline.expect b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.textual_outline.expect
new file mode 100644
index 0000000..d7b5425
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.textual_outline.expect
@@ -0,0 +1,10 @@
+class A {
+  void set foo(int value) {}
+}
+
+extension E on A {
+  void set bar(int value) {}
+}
+
+test(A a, E e) {}
+main() {}
diff --git a/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..74b9e08
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.textual_outline_modelled.expect
@@ -0,0 +1,10 @@
+class A {
+  void set foo(int value) {}
+}
+
+extension E on A {
+  void set bar(int value) {}
+}
+
+main() {}
+test(A a, E e) {}
diff --git a/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.weak.expect b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.weak.expect
new file mode 100644
index 0000000..c7f4354
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.weak.expect
@@ -0,0 +1,32 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/simple_setter_resolution.dart:16:5: Error: The setter 'foo' isn't defined for the extension 'E'.
+// Try correcting the name to the name of an existing setter, or defining a setter or field named 'foo'.
+//   e.foo = 42; // Error.
+//     ^^^
+//
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+  set foo(core::int value) → void {}
+}
+extension E on self::A {
+  set bar = self::E|set#bar;
+}
+static method E|set#bar(lowered final self::A #this, core::int value) → void {}
+static method test(self::A a, self::E e) → dynamic {
+  a.{self::A::foo} = 42;
+  self::E|set#bar(a, 42);
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_setter_resolution.dart:16:5: Error: The setter 'foo' isn't defined for the extension 'E'.
+Try correcting the name to the name of an existing setter, or defining a setter or field named 'foo'.
+  e.foo = 42; // Error.
+    ^^^";
+  self::E|set#bar(e, 42);
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.weak.outline.expect b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.weak.outline.expect
new file mode 100644
index 0000000..05a6bb6
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.weak.outline.expect
@@ -0,0 +1,19 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    ;
+  set foo(core::int value) → void
+    ;
+}
+extension E on self::A {
+  set bar = self::E|set#bar;
+}
+static method E|set#bar(lowered final self::A #this, core::int value) → void
+  ;
+static method test(self::A a, self::E e) → dynamic
+  ;
+static method main() → dynamic
+  ;
diff --git a/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.weak.transformed.expect b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.weak.transformed.expect
new file mode 100644
index 0000000..c7f4354
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/simple_setter_resolution.dart.weak.transformed.expect
@@ -0,0 +1,32 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/simple_setter_resolution.dart:16:5: Error: The setter 'foo' isn't defined for the extension 'E'.
+// Try correcting the name to the name of an existing setter, or defining a setter or field named 'foo'.
+//   e.foo = 42; // Error.
+//     ^^^
+//
+import self as self;
+import "dart:core" as core;
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+  set foo(core::int value) → void {}
+}
+extension E on self::A {
+  set bar = self::E|set#bar;
+}
+static method E|set#bar(lowered final self::A #this, core::int value) → void {}
+static method test(self::A a, self::E e) → dynamic {
+  a.{self::A::foo} = 42;
+  self::E|set#bar(a, 42);
+  invalid-expression "pkg/front_end/testcases/extension_types/simple_setter_resolution.dart:16:5: Error: The setter 'foo' isn't defined for the extension 'E'.
+Try correcting the name to the name of an existing setter, or defining a setter or field named 'foo'.
+  e.foo = 42; // Error.
+    ^^^";
+  self::E|set#bar(e, 42);
+}
+static method main() → dynamic {}
diff --git a/pkg/front_end/testcases/incremental/changing_modules_2.yaml.world.1.expect b/pkg/front_end/testcases/incremental/changing_modules_2.yaml.world.1.expect
index ad8a087..41e48cc 100644
--- a/pkg/front_end/testcases/incremental/changing_modules_2.yaml.world.1.expect
+++ b/pkg/front_end/testcases/incremental/changing_modules_2.yaml.world.1.expect
@@ -6,8 +6,8 @@
 }
 library from "package:foo/foo.dart" as foo {
 additionalExports = (a::example,
-  a::example,
-  a::a)
+  a::a,
+  a::example)
 
   export "package:example/a.dart";
 
diff --git a/pkg/front_end/testcases/incremental/changing_modules_2.yaml.world.2.expect b/pkg/front_end/testcases/incremental/changing_modules_2.yaml.world.2.expect
index ad8a087..41e48cc 100644
--- a/pkg/front_end/testcases/incremental/changing_modules_2.yaml.world.2.expect
+++ b/pkg/front_end/testcases/incremental/changing_modules_2.yaml.world.2.expect
@@ -6,8 +6,8 @@
 }
 library from "package:foo/foo.dart" as foo {
 additionalExports = (a::example,
-  a::example,
-  a::a)
+  a::a,
+  a::example)
 
   export "package:example/a.dart";
 
diff --git a/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.1.expect b/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.1.expect
index cc2970c..83a60c98 100644
--- a/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.1.expect
+++ b/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.1.expect
@@ -10,8 +10,8 @@
 }
 library from "org-dartlang-test:///libExporter.dart" as lib2 {
 additionalExports = (lib::libField,
-  lib::libField,
-  lib::requireStuffFromLibExporter)
+  lib::requireStuffFromLibExporter,
+  lib::libField)
 
   export "org-dartlang-test:///lib.dart";
 
diff --git a/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.2.expect b/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.2.expect
index cc2970c..83a60c98 100644
--- a/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.2.expect
+++ b/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.2.expect
@@ -10,8 +10,8 @@
 }
 library from "org-dartlang-test:///libExporter.dart" as lib2 {
 additionalExports = (lib::libField,
-  lib::libField,
-  lib::requireStuffFromLibExporter)
+  lib::requireStuffFromLibExporter,
+  lib::libField)
 
   export "org-dartlang-test:///lib.dart";
 
diff --git a/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.3.expect b/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.3.expect
index cc2970c..83a60c98 100644
--- a/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.3.expect
+++ b/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.3.expect
@@ -10,8 +10,8 @@
 }
 library from "org-dartlang-test:///libExporter.dart" as lib2 {
 additionalExports = (lib::libField,
-  lib::libField,
-  lib::requireStuffFromLibExporter)
+  lib::requireStuffFromLibExporter,
+  lib::libField)
 
   export "org-dartlang-test:///lib.dart";
 
diff --git a/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.4.expect b/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.4.expect
index cc2970c..83a60c98 100644
--- a/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.4.expect
+++ b/pkg/front_end/testcases/incremental/no_outline_change_43.yaml.world.4.expect
@@ -10,8 +10,8 @@
 }
 library from "org-dartlang-test:///libExporter.dart" as lib2 {
 additionalExports = (lib::libField,
-  lib::libField,
-  lib::requireStuffFromLibExporter)
+  lib::requireStuffFromLibExporter,
+  lib::libField)
 
   export "org-dartlang-test:///lib.dart";
 
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 fa7c510..0564c29 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
@@ -9,19 +9,20 @@
   synthetic constructor •() → self::StructInlineArray
     : super ffi::Struct::•()
     ;
-  @#C2
+  @#C3
   external get a0() → ffi::Array<ffi::Uint8>;
-  @#C2
+  @#C3
   external set a0(ffi::Array<ffi::Uint8> #externalFieldValue) → void;
 }
 static method main() → dynamic {}
 
 constants  {
   #C1 = 8
-  #C2 = ffi::_ArraySize<ffi::NativeType> {dimension1:#C1}
+  #C2 = null
+  #C3 = ffi::_ArraySize<ffi::NativeType> {dimension1:#C1, dimension2:#C2, dimension3:#C2, dimension4:#C2, dimension5:#C2, dimensions:#C2}
 }
 
 
 Constructor coverage from constants:
 org-dartlang-testcase:///ffi_struct_inline_array.dart:
-- _ArraySize. (from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/ffi_patch.dart:133:9)
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/ffi_patch.dart:156: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 77d29d6..58634b4 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
@@ -23,7 +23,7 @@
     return new ffi::Array::_<ffi::Array<ffi::Uint8>>( block {
       core::Object #typedDataBase = this.{ffi::Struct::_addressOf};
       core::int #offset = (#C14).{core::List::[]}(ffi::_abi());
-    } =>#typedDataBase is ffi::Pointer<dynamic> ?{core::Object} ffi::_fromAddress<ffi::Uint8>(#typedDataBase.{ffi::Pointer::address}.{core::num::+}(#offset)) : let typ::TypedData #typedData = _in::unsafeCast<typ::TypedData>(#typedDataBase) in #typedData.{typ::TypedData::buffer}.{typ::ByteBuffer::asUint8List}(#typedData.{typ::TypedData::offsetInBytes}.{core::num::+}(#offset), (#C8).{core::List::[]}(ffi::_abi())), #C3);
+    } =>#typedDataBase is ffi::Pointer<dynamic> ?{core::Object} ffi::_fromAddress<ffi::Uint8>(#typedDataBase.{ffi::Pointer::address}.{core::num::+}(#offset)) : let typ::TypedData #typedData = _in::unsafeCast<typ::TypedData>(#typedDataBase) in #typedData.{typ::TypedData::buffer}.{typ::ByteBuffer::asUint8List}(#typedData.{typ::TypedData::offsetInBytes}.{core::num::+}(#offset), (#C8).{core::List::[]}(ffi::_abi())), #C3, #C15);
   @#C12
   set a0(ffi::Array<ffi::Uint8> #externalFieldValue) → void
     return ffi::_memCopy(this.{ffi::Struct::_addressOf}, (#C14).{core::List::[]}(ffi::_abi()), #externalFieldValue.{ffi::Array::_typedDataBase}, #C13, (#C8).{core::List::[]}(ffi::_abi()));
@@ -42,12 +42,13 @@
   #C9 = "vm:entry-point"
   #C10 = null
   #C11 = core::pragma {name:#C9, options:#C10}
-  #C12 = ffi::_ArraySize<ffi::NativeType> {dimension1:#C3}
+  #C12 = ffi::_ArraySize<ffi::NativeType> {dimension1:#C3, dimension2:#C10, dimension3:#C10, dimension4:#C10, dimension5:#C10, dimensions:#C10}
   #C13 = 0
   #C14 = <core::int*>[#C13, #C13, #C13]
+  #C15 = <core::int*>[]
 }
 
 
 Constructor coverage from constants:
 org-dartlang-testcase:///ffi_struct_inline_array.dart:
-- _ArraySize. (from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/ffi_patch.dart:133:9)
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/ffi_patch.dart:156: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 5125b89..3637817 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
@@ -9,19 +9,20 @@
   synthetic constructor •() → self::StructInlineArray
     : super ffi::Struct::•()
     ;
-  @#C2
+  @#C3
   external get a0() → ffi::Array<ffi::Uint8>;
-  @#C2
+  @#C3
   external set a0(ffi::Array<ffi::Uint8> #externalFieldValue) → void;
 }
 static method main() → dynamic {}
 
 constants  {
   #C1 = 8
-  #C2 = ffi::_ArraySize<ffi::NativeType*> {dimension1:#C1}
+  #C2 = null
+  #C3 = ffi::_ArraySize<ffi::NativeType*> {dimension1:#C1, dimension2:#C2, dimension3:#C2, dimension4:#C2, dimension5:#C2, dimensions:#C2}
 }
 
 
 Constructor coverage from constants:
 org-dartlang-testcase:///ffi_struct_inline_array.dart:
-- _ArraySize. (from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/ffi_patch.dart:133:9)
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/ffi_patch.dart:156:9)
diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.outline.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.outline.expect
index 75350ad..209ea1d 100644
--- a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array.dart.weak.outline.expect
@@ -18,6 +18,6 @@
 
 
 Extra constant evaluation status:
-Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array.dart:10:4 -> InstanceConstant(const _ArraySize<NativeType*>{_ArraySize.dimension1: 8})
-Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array.dart:10:4 -> InstanceConstant(const _ArraySize<NativeType*>{_ArraySize.dimension1: 8})
+Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array.dart:10:4 -> InstanceConstant(const _ArraySize<NativeType*>{_ArraySize.dimension1: 8, _ArraySize.dimension2: null, _ArraySize.dimension3: null, _ArraySize.dimension4: null, _ArraySize.dimension5: null, _ArraySize.dimensions: null})
+Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array.dart:10:4 -> InstanceConstant(const _ArraySize<NativeType*>{_ArraySize.dimension1: 8, _ArraySize.dimension2: null, _ArraySize.dimension3: null, _ArraySize.dimension4: null, _ArraySize.dimension5: null, _ArraySize.dimensions: null})
 Extra constant evaluation: evaluated: 2, effectively constant: 2
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 bd41225..5ea5b51 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
@@ -23,7 +23,7 @@
     return new ffi::Array::_<ffi::Array<ffi::Uint8>>( block {
       core::Object #typedDataBase = this.{ffi::Struct::_addressOf};
       core::int #offset = (#C14).{core::List::[]}(ffi::_abi());
-    } =>#typedDataBase is ffi::Pointer<dynamic> ?{core::Object} ffi::_fromAddress<ffi::Uint8>(#typedDataBase.{ffi::Pointer::address}.{core::num::+}(#offset)) : let typ::TypedData #typedData = _in::unsafeCast<typ::TypedData>(#typedDataBase) in #typedData.{typ::TypedData::buffer}.{typ::ByteBuffer::asUint8List}(#typedData.{typ::TypedData::offsetInBytes}.{core::num::+}(#offset), (#C8).{core::List::[]}(ffi::_abi())), #C3);
+    } =>#typedDataBase is ffi::Pointer<dynamic> ?{core::Object} ffi::_fromAddress<ffi::Uint8>(#typedDataBase.{ffi::Pointer::address}.{core::num::+}(#offset)) : let typ::TypedData #typedData = _in::unsafeCast<typ::TypedData>(#typedDataBase) in #typedData.{typ::TypedData::buffer}.{typ::ByteBuffer::asUint8List}(#typedData.{typ::TypedData::offsetInBytes}.{core::num::+}(#offset), (#C8).{core::List::[]}(ffi::_abi())), #C3, #C15);
   @#C12
   set a0(ffi::Array<ffi::Uint8> #externalFieldValue) → void
     return ffi::_memCopy(this.{ffi::Struct::_addressOf}, (#C14).{core::List::[]}(ffi::_abi()), #externalFieldValue.{ffi::Array::_typedDataBase}, #C13, (#C8).{core::List::[]}(ffi::_abi()));
@@ -42,12 +42,13 @@
   #C9 = "vm:entry-point"
   #C10 = null
   #C11 = core::pragma {name:#C9, options:#C10}
-  #C12 = ffi::_ArraySize<ffi::NativeType*> {dimension1:#C3}
+  #C12 = ffi::_ArraySize<ffi::NativeType*> {dimension1:#C3, dimension2:#C10, dimension3:#C10, dimension4:#C10, dimension5:#C10, dimensions:#C10}
   #C13 = 0
   #C14 = <core::int*>[#C13, #C13, #C13]
+  #C15 = <core::int*>[]
 }
 
 
 Constructor coverage from constants:
 org-dartlang-testcase:///ffi_struct_inline_array.dart:
-- _ArraySize. (from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/ffi_patch.dart:133:9)
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/ffi_patch.dart:156:9)
diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart
new file mode 100644
index 0000000..08618c8
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart
@@ -0,0 +1,21 @@
+// 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 "package:ffi/ffi.dart";
+
+class StructInlineArrayMultiDimensional extends Struct {
+  @Array(2, 2, 2)
+  external Array<Array<Array<Uint8>>> a0;
+}
+
+main() {
+  final pointer = calloc<StructInlineArrayMultiDimensional>();
+  final struct = pointer.ref;
+  final array = struct.a0;
+  final subArray = array[0];
+  array[1] = subArray;
+  calloc.free(pointer);
+}
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
new file mode 100644
index 0000000..7d5a7e1
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.expect
@@ -0,0 +1,36 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:ffi" as ffi;
+
+import "dart:ffi";
+import "package:ffi/ffi.dart";
+
+class StructInlineArrayMultiDimensional extends ffi::Struct {
+  synthetic constructor •() → self::StructInlineArrayMultiDimensional
+    : super ffi::Struct::•()
+    ;
+  @#C3
+  external get a0() → ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>>;
+  @#C3
+  external set a0(ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>> #externalFieldValue) → void;
+}
+static method main() → dynamic {
+  final ffi::Pointer<self::StructInlineArrayMultiDimensional> pointer = ffi::AllocatorAlloc|call<self::StructInlineArrayMultiDimensional>(#C4);
+  final self::StructInlineArrayMultiDimensional struct = ffi::StructPointer|get#ref<self::StructInlineArrayMultiDimensional>(pointer);
+  final ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>> array = struct.{self::StructInlineArrayMultiDimensional::a0};
+  final ffi::Array<ffi::Array<ffi::Uint8>> subArray = ffi::ArrayArray|[]<ffi::Array<ffi::Uint8>>(array, 0);
+  ffi::ArrayArray|[]=<ffi::Array<ffi::Uint8>>(array, 1, subArray);
+  (#C4).{ffi::Allocator::free}(pointer);
+}
+
+constants  {
+  #C1 = 2
+  #C2 = null
+  #C3 = ffi::_ArraySize<ffi::NativeType> {dimension1:#C1, dimension2:#C1, dimension3:#C1, dimension4:#C2, dimension5:#C2, dimensions:#C2}
+  #C4 = all::_CallocAllocator {}
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/ffi_patch.dart:156: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
new file mode 100644
index 0000000..c6d3600
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.strong.transformed.expect
@@ -0,0 +1,86 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:typed_data" as typ;
+import "dart:_internal" as _in;
+
+import "dart:ffi";
+import "package:ffi/ffi.dart";
+
+@#C7
+class StructInlineArrayMultiDimensional extends ffi::Struct {
+  static final field core::int* #sizeOf = (#C8).{core::List::[]}(ffi::_abi())/*isLegacy*/;
+  synthetic constructor •() → self::StructInlineArrayMultiDimensional
+    : super ffi::Struct::•()
+    ;
+  @#C11
+  constructor #fromTypedDataBase(dynamic #pointer) → dynamic
+    : super ffi::Struct::_fromPointer(#pointer)
+    ;
+  @#C13
+  get a0() → ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>>
+    return new ffi::Array::_<ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>>>( block {
+      core::Object #typedDataBase = this.{ffi::Struct::_addressOf};
+      core::int #offset = (#C15).{core::List::[]}(ffi::_abi());
+    } =>#typedDataBase is ffi::Pointer<dynamic> ?{core::Object} ffi::_fromAddress<ffi::Array<ffi::Array<ffi::Uint8>>>(#typedDataBase.{ffi::Pointer::address}.{core::num::+}(#offset)) : let typ::TypedData #typedData = _in::unsafeCast<typ::TypedData>(#typedDataBase) in #typedData.{typ::TypedData::buffer}.{typ::ByteBuffer::asUint8List}(#typedData.{typ::TypedData::offsetInBytes}.{core::num::+}(#offset), (#C8).{core::List::[]}(ffi::_abi())), #C12, #C16);
+  @#C13
+  set a0(ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>> #externalFieldValue) → void
+    return ffi::_memCopy(this.{ffi::Struct::_addressOf}, (#C15).{core::List::[]}(ffi::_abi()), #externalFieldValue.{ffi::Array::_typedDataBase}, #C14, (#C8).{core::List::[]}(ffi::_abi()));
+}
+static method main() → dynamic {
+  final ffi::Pointer<self::StructInlineArrayMultiDimensional> pointer = (#C17).{ffi::Allocator::allocate}<self::StructInlineArrayMultiDimensional>(self::StructInlineArrayMultiDimensional::#sizeOf);
+  final self::StructInlineArrayMultiDimensional struct = new self::StructInlineArrayMultiDimensional::#fromTypedDataBase(pointer!);
+  final ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>> array = struct.{self::StructInlineArrayMultiDimensional::a0};
+  final ffi::Array<ffi::Array<ffi::Uint8>> subArray = block {
+    ffi::Array<dynamic> #array = array!;
+    core::int #index = 0!;
+    #array.{ffi::Array::_checkIndex}(#index);
+    core::int #singleElementSize = #C18;
+    core::int #elementSize = #singleElementSize.{core::num::*}(#array.{ffi::Array::_nestedDimensionsFlattened});
+    core::int #offset = #elementSize.{core::num::*}(#index);
+  } =>new ffi::Array::_<ffi::Array<ffi::Uint8>>( block {
+    core::Object #typedDataBase = #array.{ffi::Array::_typedDataBase};
+    core::int #offset = #offset;
+  } =>#typedDataBase is ffi::Pointer<dynamic> ?{core::Object} ffi::_fromAddress<ffi::Array<ffi::Uint8>>(#typedDataBase.{ffi::Pointer::address}.{core::num::+}(#offset)) : let typ::TypedData #typedData = _in::unsafeCast<typ::TypedData>(#typedDataBase) in #typedData.{typ::TypedData::buffer}.{typ::ByteBuffer::asUint8List}(#typedData.{typ::TypedData::offsetInBytes}.{core::num::+}(#offset), #elementSize), #array.{ffi::Array::_nestedDimensionsFirst}, #array.{ffi::Array::_nestedDimensionsRest});
+  block {
+    ffi::Array<dynamic> #array = array!;
+    core::int #index = 1!;
+    #array.{ffi::Array::_checkIndex}(#index);
+    core::int #singleElementSize = #C18;
+    core::int #elementSize = #singleElementSize.{core::num::*}(#array.{ffi::Array::_nestedDimensionsFlattened});
+    core::int #offset = #elementSize.{core::num::*}(#index);
+  } =>ffi::_memCopy(#array.{ffi::Array::_typedDataBase}, #offset, subArray.{ffi::Array::_typedDataBase}, #C14, #elementSize);
+  (#C17).{ffi::Allocator::free}(pointer);
+}
+
+constants  {
+  #C1 = "vm:ffi:struct-fields"
+  #C2 = TypeLiteralConstant(ffi::Uint8)
+  #C3 = 8
+  #C4 = ffi::_FfiInlineArray {elementType:#C2, length:#C3}
+  #C5 = <core::Type>[#C4]
+  #C6 = ffi::_FfiStructLayout {fieldTypes:#C5}
+  #C7 = core::pragma {name:#C1, options:#C6}
+  #C8 = <core::int*>[#C3, #C3, #C3]
+  #C9 = "vm:entry-point"
+  #C10 = null
+  #C11 = core::pragma {name:#C9, options:#C10}
+  #C12 = 2
+  #C13 = ffi::_ArraySize<ffi::NativeType> {dimension1:#C12, dimension2:#C12, dimension3:#C12, dimension4:#C10, dimension5:#C10, dimensions:#C10}
+  #C14 = 0
+  #C15 = <core::int*>[#C14, #C14, #C14]
+  #C16 = <core::int*>[#C12, #C12]
+  #C17 = all::_CallocAllocator {}
+  #C18 = 1
+}
+
+Extra constant evaluation status:
+Evaluated: NullCheck @ org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:18:25 -> IntConstant(0)
+Evaluated: NullCheck @ org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:19:8 -> IntConstant(1)
+Extra constant evaluation: evaluated: 110, effectively constant: 2
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/ffi_patch.dart:156:9)
diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.textual_outline.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.textual_outline.expect
new file mode 100644
index 0000000..677a6db
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.textual_outline.expect
@@ -0,0 +1,9 @@
+import 'dart:ffi';
+import "package:ffi/ffi.dart";
+
+class StructInlineArrayMultiDimensional extends Struct {
+  @Array(2, 2, 2)
+  external Array<Array<Array<Uint8>>> a0;
+}
+
+main() {}
diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..b7c36aa
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.textual_outline_modelled.expect
@@ -0,0 +1,9 @@
+import "package:ffi/ffi.dart";
+import 'dart:ffi';
+
+class StructInlineArrayMultiDimensional extends Struct {
+  @Array(2, 2, 2)
+  external Array<Array<Array<Uint8>>> a0;
+}
+
+main() {}
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
new file mode 100644
index 0000000..4f90a79
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.expect
@@ -0,0 +1,36 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:ffi" as ffi;
+
+import "dart:ffi";
+import "package:ffi/ffi.dart";
+
+class StructInlineArrayMultiDimensional extends ffi::Struct {
+  synthetic constructor •() → self::StructInlineArrayMultiDimensional
+    : super ffi::Struct::•()
+    ;
+  @#C3
+  external get a0() → ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>>;
+  @#C3
+  external set a0(ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>> #externalFieldValue) → void;
+}
+static method main() → dynamic {
+  final ffi::Pointer<self::StructInlineArrayMultiDimensional> pointer = ffi::AllocatorAlloc|call<self::StructInlineArrayMultiDimensional>(#C4);
+  final self::StructInlineArrayMultiDimensional struct = ffi::StructPointer|get#ref<self::StructInlineArrayMultiDimensional>(pointer);
+  final ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>> array = struct.{self::StructInlineArrayMultiDimensional::a0};
+  final ffi::Array<ffi::Array<ffi::Uint8>> subArray = ffi::ArrayArray|[]<ffi::Array<ffi::Uint8>>(array, 0);
+  ffi::ArrayArray|[]=<ffi::Array<ffi::Uint8>>(array, 1, subArray);
+  (#C4).{ffi::Allocator::free}(pointer);
+}
+
+constants  {
+  #C1 = 2
+  #C2 = null
+  #C3 = ffi::_ArraySize<ffi::NativeType*> {dimension1:#C1, dimension2:#C1, dimension3:#C1, dimension4:#C2, dimension5:#C2, dimensions:#C2}
+  #C4 = all::_CallocAllocator {}
+}
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/ffi_patch.dart:156:9)
diff --git a/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.outline.expect b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.outline.expect
new file mode 100644
index 0000000..4cd5816
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.outline.expect
@@ -0,0 +1,23 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:ffi" as ffi;
+
+import "dart:ffi";
+import "package:ffi/ffi.dart";
+
+class StructInlineArrayMultiDimensional extends ffi::Struct {
+  synthetic constructor •() → self::StructInlineArrayMultiDimensional
+    ;
+  @ffi::_ArraySize::•<ffi::NativeType>(2, 2, 2)
+  external get a0() → ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>>;
+  @ffi::_ArraySize::•<ffi::NativeType>(2, 2, 2)
+  external set a0(ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>> #externalFieldValue) → void;
+}
+static method main() → dynamic
+  ;
+
+
+Extra constant evaluation status:
+Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:10:4 -> InstanceConstant(const _ArraySize<NativeType*>{_ArraySize.dimension1: 2, _ArraySize.dimension2: 2, _ArraySize.dimension3: 2, _ArraySize.dimension4: null, _ArraySize.dimension5: null, _ArraySize.dimensions: null})
+Evaluated: ConstructorInvocation @ org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:10:4 -> InstanceConstant(const _ArraySize<NativeType*>{_ArraySize.dimension1: 2, _ArraySize.dimension2: 2, _ArraySize.dimension3: 2, _ArraySize.dimension4: null, _ArraySize.dimension5: null, _ArraySize.dimensions: null})
+Extra constant evaluation: evaluated: 2, effectively constant: 2
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
new file mode 100644
index 0000000..d65caa1
--- /dev/null
+++ b/pkg/front_end/testcases/nnbd/ffi_struct_inline_array_multi_dimensional.dart.weak.transformed.expect
@@ -0,0 +1,86 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:typed_data" as typ;
+import "dart:_internal" as _in;
+
+import "dart:ffi";
+import "package:ffi/ffi.dart";
+
+@#C7
+class StructInlineArrayMultiDimensional extends ffi::Struct {
+  static final field core::int* #sizeOf = (#C8).{core::List::[]}(ffi::_abi())/*isLegacy*/;
+  synthetic constructor •() → self::StructInlineArrayMultiDimensional
+    : super ffi::Struct::•()
+    ;
+  @#C11
+  constructor #fromTypedDataBase(dynamic #pointer) → dynamic
+    : super ffi::Struct::_fromPointer(#pointer)
+    ;
+  @#C13
+  get a0() → ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>>
+    return new ffi::Array::_<ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>>>( block {
+      core::Object #typedDataBase = this.{ffi::Struct::_addressOf};
+      core::int #offset = (#C15).{core::List::[]}(ffi::_abi());
+    } =>#typedDataBase is ffi::Pointer<dynamic> ?{core::Object} ffi::_fromAddress<ffi::Array<ffi::Array<ffi::Uint8>>>(#typedDataBase.{ffi::Pointer::address}.{core::num::+}(#offset)) : let typ::TypedData #typedData = _in::unsafeCast<typ::TypedData>(#typedDataBase) in #typedData.{typ::TypedData::buffer}.{typ::ByteBuffer::asUint8List}(#typedData.{typ::TypedData::offsetInBytes}.{core::num::+}(#offset), (#C8).{core::List::[]}(ffi::_abi())), #C12, #C16);
+  @#C13
+  set a0(ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>> #externalFieldValue) → void
+    return ffi::_memCopy(this.{ffi::Struct::_addressOf}, (#C15).{core::List::[]}(ffi::_abi()), #externalFieldValue.{ffi::Array::_typedDataBase}, #C14, (#C8).{core::List::[]}(ffi::_abi()));
+}
+static method main() → dynamic {
+  final ffi::Pointer<self::StructInlineArrayMultiDimensional> pointer = (#C17).{ffi::Allocator::allocate}<self::StructInlineArrayMultiDimensional>(self::StructInlineArrayMultiDimensional::#sizeOf);
+  final self::StructInlineArrayMultiDimensional struct = new self::StructInlineArrayMultiDimensional::#fromTypedDataBase(pointer!);
+  final ffi::Array<ffi::Array<ffi::Array<ffi::Uint8>>> array = struct.{self::StructInlineArrayMultiDimensional::a0};
+  final ffi::Array<ffi::Array<ffi::Uint8>> subArray = block {
+    ffi::Array<dynamic> #array = array!;
+    core::int #index = 0!;
+    #array.{ffi::Array::_checkIndex}(#index);
+    core::int #singleElementSize = #C18;
+    core::int #elementSize = #singleElementSize.{core::num::*}(#array.{ffi::Array::_nestedDimensionsFlattened});
+    core::int #offset = #elementSize.{core::num::*}(#index);
+  } =>new ffi::Array::_<ffi::Array<ffi::Uint8>>( block {
+    core::Object #typedDataBase = #array.{ffi::Array::_typedDataBase};
+    core::int #offset = #offset;
+  } =>#typedDataBase is ffi::Pointer<dynamic> ?{core::Object} ffi::_fromAddress<ffi::Array<ffi::Uint8>>(#typedDataBase.{ffi::Pointer::address}.{core::num::+}(#offset)) : let typ::TypedData #typedData = _in::unsafeCast<typ::TypedData>(#typedDataBase) in #typedData.{typ::TypedData::buffer}.{typ::ByteBuffer::asUint8List}(#typedData.{typ::TypedData::offsetInBytes}.{core::num::+}(#offset), #elementSize), #array.{ffi::Array::_nestedDimensionsFirst}, #array.{ffi::Array::_nestedDimensionsRest});
+  block {
+    ffi::Array<dynamic> #array = array!;
+    core::int #index = 1!;
+    #array.{ffi::Array::_checkIndex}(#index);
+    core::int #singleElementSize = #C18;
+    core::int #elementSize = #singleElementSize.{core::num::*}(#array.{ffi::Array::_nestedDimensionsFlattened});
+    core::int #offset = #elementSize.{core::num::*}(#index);
+  } =>ffi::_memCopy(#array.{ffi::Array::_typedDataBase}, #offset, subArray.{ffi::Array::_typedDataBase}, #C14, #elementSize);
+  (#C17).{ffi::Allocator::free}(pointer);
+}
+
+constants  {
+  #C1 = "vm:ffi:struct-fields"
+  #C2 = TypeLiteralConstant(ffi::Uint8)
+  #C3 = 8
+  #C4 = ffi::_FfiInlineArray {elementType:#C2, length:#C3}
+  #C5 = <core::Type>[#C4]
+  #C6 = ffi::_FfiStructLayout {fieldTypes:#C5}
+  #C7 = core::pragma {name:#C1, options:#C6}
+  #C8 = <core::int*>[#C3, #C3, #C3]
+  #C9 = "vm:entry-point"
+  #C10 = null
+  #C11 = core::pragma {name:#C9, options:#C10}
+  #C12 = 2
+  #C13 = ffi::_ArraySize<ffi::NativeType*> {dimension1:#C12, dimension2:#C12, dimension3:#C12, dimension4:#C10, dimension5:#C10, dimensions:#C10}
+  #C14 = 0
+  #C15 = <core::int*>[#C14, #C14, #C14]
+  #C16 = <core::int*>[#C12, #C12]
+  #C17 = all::_CallocAllocator {}
+  #C18 = 1
+}
+
+Extra constant evaluation status:
+Evaluated: NullCheck @ org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:18:25 -> IntConstant(0)
+Evaluated: NullCheck @ org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:19:8 -> IntConstant(1)
+Extra constant evaluation: evaluated: 110, effectively constant: 2
+
+
+Constructor coverage from constants:
+org-dartlang-testcase:///ffi_struct_inline_array_multi_dimensional.dart:
+- _ArraySize. (from org-dartlang-sdk:///sdk/lib/_internal/vm/lib/ffi_patch.dart:156:9)
diff --git a/pkg/front_end/testcases/outline.status b/pkg/front_end/testcases/outline.status
index 63e873f..533b969 100644
--- a/pkg/front_end/testcases/outline.status
+++ b/pkg/front_end/testcases/outline.status
@@ -3,7 +3,10 @@
 # BSD-style license that can be found in the LICENSE.md file.
 
 extension_types/simple: ExpectationFileMismatchSerialized
+extension_types/simple_getter_resolution: ExpectationFileMismatchSerialized
 extension_types/simple_method_resolution: ExpectationFileMismatchSerialized
+extension_types/simple_operator_resolution: ExpectationFileMismatchSerialized
+extension_types/simple_setter_resolution: ExpectationFileMismatchSerialized
 general/abstract_members: TypeCheckError
 general/bug30695: TypeCheckError
 general/covariant_field: TypeCheckError
diff --git a/pkg/front_end/testcases/strong.status b/pkg/front_end/testcases/strong.status
index 757cc84..c0d86e6 100644
--- a/pkg/front_end/testcases/strong.status
+++ b/pkg/front_end/testcases/strong.status
@@ -7,7 +7,10 @@
 # strong-mode enabled.
 
 extension_types/simple: ExpectationFileMismatchSerialized # Expected.
+extension_types/simple_getter_resolution: ExpectationFileMismatchSerialized # Expected.
 extension_types/simple_method_resolution: ExpectationFileMismatchSerialized # Expected.
+extension_types/simple_operator_resolution: ExpectationFileMismatchSerialized # Expected.
+extension_types/simple_setter_resolution: ExpectationFileMismatchSerialized # Expected.
 late_lowering/covariant_late_field: TypeCheckError
 nnbd/covariant_late_field: TypeCheckError
 nnbd/getter_vs_setter_type: TypeCheckError
diff --git a/pkg/front_end/testcases/text_serialization.status b/pkg/front_end/testcases/text_serialization.status
index 3aabae1..2538568 100644
--- a/pkg/front_end/testcases/text_serialization.status
+++ b/pkg/front_end/testcases/text_serialization.status
@@ -7,7 +7,10 @@
 # Kernel files are produced by compiling Dart code via Fasta.
 
 extension_types/simple: ExpectationFileMismatchSerialized # Expected.
+extension_types/simple_getter_resolution: ExpectationFileMismatchSerialized # Expected.
 extension_types/simple_method_resolution: ExpectationFileMismatchSerialized # Expected.
+extension_types/simple_operator_resolution: ExpectationFileMismatchSerialized # Expected.
+extension_types/simple_setter_resolution: ExpectationFileMismatchSerialized # Expected.
 extensions/call_methods: TypeCheckError
 extensions/extension_setter_error: TypeCheckError
 extensions/instance_access_of_static: RuntimeError
diff --git a/pkg/front_end/testcases/weak.status b/pkg/front_end/testcases/weak.status
index 779ddea..31f6de1 100644
--- a/pkg/front_end/testcases/weak.status
+++ b/pkg/front_end/testcases/weak.status
@@ -11,7 +11,10 @@
 # regress/utf_16_le_content.crash: Crash # semi fuzz fails but isn't currently enabled by default.
 
 extension_types/simple: ExpectationFileMismatchSerialized # Expected.
+extension_types/simple_getter_resolution: ExpectationFileMismatchSerialized # Expected.
 extension_types/simple_method_resolution: ExpectationFileMismatchSerialized # Expected.
+extension_types/simple_operator_resolution: ExpectationFileMismatchSerialized # Expected.
+extension_types/simple_setter_resolution: ExpectationFileMismatchSerialized # Expected.
 extensions/call_methods: TypeCheckError
 extensions/extension_setter_error: TypeCheckError
 extensions/instance_access_of_static: RuntimeError
diff --git a/pkg/kernel/binary.md b/pkg/kernel/binary.md
index b19ad56..8c5f5a2 100644
--- a/pkg/kernel/binary.md
+++ b/pkg/kernel/binary.md
@@ -147,7 +147,7 @@
 
 type ComponentFile {
   UInt32 magic = 0x90ABCDEF;
-  UInt32 formatVersion = 57;
+  UInt32 formatVersion = 58;
   Byte[10] shortSdkHash;
   List<String> problemsAsJson; // Described in problems.md.
   Library[] libraries;
diff --git a/pkg/kernel/lib/ast.dart b/pkg/kernel/lib/ast.dart
index a52a42c..a5bbdc5 100644
--- a/pkg/kernel/lib/ast.dart
+++ b/pkg/kernel/lib/ast.dart
@@ -348,6 +348,52 @@
     }
     return node as Extension;
   }
+
+  bool get isConsistent {
+    NamedNode? node = _node;
+    if (node != null) {
+      if (node.reference != this &&
+          (node is! Field || node.setterReference != this)) {
+        // The reference of a [NamedNode] must point to this reference, or
+        // if the node is a [Field] the setter reference must point to this
+        // reference.
+        return false;
+      }
+    }
+    if (canonicalName != null && canonicalName!.reference != this) {
+      return false;
+    }
+    return true;
+  }
+
+  String getInconsistency() {
+    StringBuffer sb = new StringBuffer();
+    sb.write('Reference ${this} (${hashCode}):');
+    NamedNode? node = _node;
+    if (node != null) {
+      if (node is Field) {
+        if (node.getterReference != this && node.setterReference != this) {
+          sb.write(' _node=${node} (${node.runtimeType}:${node.hashCode})');
+          sb.write(' _node.getterReference='
+              '${node.getterReference} (${node.getterReference.hashCode})');
+          sb.write(' _node.setterReference='
+              '${node.setterReference} (${node.setterReference.hashCode})');
+        }
+      } else {
+        if (node.reference != this) {
+          sb.write(' _node=${node} (${node.runtimeType}:${node.hashCode})');
+          sb.write(' _node.reference='
+              '${node.reference} (${node.reference.hashCode})');
+        }
+      }
+    }
+    if (canonicalName != null && canonicalName!.reference != this) {
+      sb.write(' canonicalName=${canonicalName} (${canonicalName.hashCode})');
+      sb.write(' canonicalName.reference='
+          '${canonicalName!.reference} (${canonicalName!.reference.hashCode})');
+    }
+    return sb.toString();
+  }
 }
 
 // ------------------------------------------------------------------------
@@ -13140,10 +13186,7 @@
 
 /// Returns the canonical name of [member], or throws an exception if the
 /// member has not been assigned a canonical name yet.
-///
-/// Returns `null` if the member is `null`.
-CanonicalName? getCanonicalNameOfMemberGetter(Member? member) {
-  if (member == null) return null;
+CanonicalName getCanonicalNameOfMemberGetter(Member member) {
   CanonicalName? canonicalName;
   if (member is Field) {
     canonicalName = member.getterCanonicalName;
@@ -13158,10 +13201,7 @@
 
 /// Returns the canonical name of [member], or throws an exception if the
 /// member has not been assigned a canonical name yet.
-///
-/// Returns `null` if the member is `null`.
-CanonicalName? getCanonicalNameOfMemberSetter(Member? member) {
-  if (member == null) return null;
+CanonicalName getCanonicalNameOfMemberSetter(Member member) {
   CanonicalName? canonicalName;
   if (member is Field) {
     canonicalName = member.setterCanonicalName;
@@ -13176,38 +13216,29 @@
 
 /// Returns the canonical name of [class_], or throws an exception if the
 /// class has not been assigned a canonical name yet.
-///
-/// Returns `null` if the class is `null`.
-CanonicalName? getCanonicalNameOfClass(Class? class_) {
-  if (class_ == null) return null;
+CanonicalName getCanonicalNameOfClass(Class class_) {
   if (class_.canonicalName == null) {
     throw '$class_ has no canonical name';
   }
-  return class_.canonicalName;
+  return class_.canonicalName!;
 }
 
 /// Returns the canonical name of [extension], or throws an exception if the
 /// class has not been assigned a canonical name yet.
-///
-/// Returns `null` if the extension is `null`.
-CanonicalName? getCanonicalNameOfExtension(Extension? extension) {
-  if (extension == null) return null;
+CanonicalName getCanonicalNameOfExtension(Extension extension) {
   if (extension.canonicalName == null) {
     throw '$extension has no canonical name';
   }
-  return extension.canonicalName;
+  return extension.canonicalName!;
 }
 
 /// Returns the canonical name of [library], or throws an exception if the
 /// library has not been assigned a canonical name yet.
-///
-/// Returns `null` if the library is `null`.
-CanonicalName? getCanonicalNameOfLibrary(Library? library) {
-  if (library == null) return null;
+CanonicalName getCanonicalNameOfLibrary(Library library) {
   if (library.canonicalName == null) {
     throw '$library has no canonical name';
   }
-  return library.canonicalName;
+  return library.canonicalName!;
 }
 
 /// Murmur-inspired hashing, with a fall-back to Jenkins-inspired hashing when
@@ -13343,14 +13374,11 @@
 
 /// Returns the canonical name of [typedef_], or throws an exception if the
 /// typedef has not been assigned a canonical name yet.
-///
-/// Returns `null` if the typedef is `null`.
-CanonicalName? getCanonicalNameOfTypedef(Typedef? typedef_) {
-  if (typedef_ == null) return null;
+CanonicalName getCanonicalNameOfTypedef(Typedef typedef_) {
   if (typedef_.canonicalName == null) {
     throw '$typedef_ has no canonical name';
   }
-  return typedef_.canonicalName;
+  return typedef_.canonicalName!;
 }
 
 /// Annotation describing information which is not part of Dart semantics; in
diff --git a/pkg/kernel/lib/binary/ast_from_binary.dart b/pkg/kernel/lib/binary/ast_from_binary.dart
index 5f9a62f..3260625 100644
--- a/pkg/kernel/lib/binary/ast_from_binary.dart
+++ b/pkg/kernel/lib/binary/ast_from_binary.dart
@@ -716,8 +716,6 @@
       for (CanonicalName child in parentChildren) {
         if (child.name != '@methods' &&
             child.name != '@typedefs' &&
-            child.name != '@fields' &&
-            child.name != '@=fields' &&
             child.name != '@getters' &&
             child.name != '@setters' &&
             child.name != '@factories' &&
diff --git a/pkg/kernel/lib/binary/ast_to_binary.dart b/pkg/kernel/lib/binary/ast_to_binary.dart
index 2c431e2..c3de8d3 100644
--- a/pkg/kernel/lib/binary/ast_to_binary.dart
+++ b/pkg/kernel/lib/binary/ast_to_binary.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.
 
-// @dart = 2.9
-
 library kernel.ast_to_binary;
 
 import 'dart:convert';
@@ -20,34 +18,34 @@
 /// A [BinaryPrinter] can be used to write one file and must then be
 /// discarded.
 class BinaryPrinter implements Visitor<void>, BinarySink {
-  VariableIndexer _variableIndexer;
-  LabelIndexer _labelIndexer;
-  SwitchCaseIndexer _switchCaseIndexer;
+  VariableIndexer? _variableIndexer;
+  LabelIndexer? _labelIndexer;
+  SwitchCaseIndexer? _switchCaseIndexer;
   final TypeParameterIndexer _typeParameterIndexer = new TypeParameterIndexer();
   final StringIndexer stringIndexer;
-  ConstantIndexer _constantIndexer;
+  late ConstantIndexer _constantIndexer;
   final UriIndexer _sourceUriIndexer = new UriIndexer();
   bool _currentlyInNonimplementation = false;
   final List<bool> _sourcesFromRealImplementation = <bool>[];
   final List<bool> _sourcesUsedInLibrary = <bool>[];
   Map<LibraryDependency, int> _libraryDependencyIndex =
       <LibraryDependency, int>{};
-  NonNullableByDefaultCompiledMode compilationMode;
+  NonNullableByDefaultCompiledMode? compilationMode;
 
-  List<_MetadataSubsection> _metadataSubsections;
+  List<_MetadataSubsection>? _metadataSubsections;
 
   final BufferedSink _mainSink;
   final BufferedSink _metadataSink;
   final BytesSink _constantsBytesSink;
-  BufferedSink _constantsSink;
-  BufferedSink _sink;
+  late BufferedSink _constantsSink;
+  late BufferedSink _sink;
   final bool includeSources;
   final bool includeOffsets;
-  final LibraryFilter libraryFilter;
+  final LibraryFilter? libraryFilter;
 
-  List<int> libraryOffsets;
-  List<int> classOffsets;
-  List<int> procedureOffsets;
+  late List<int> libraryOffsets;
+  late List<int> classOffsets;
+  late List<int> procedureOffsets;
   int _binaryOffsetForSourceTable = -1;
   int _binaryOffsetForLinkTable = -1;
   int _binaryOffsetForMetadataPayloads = -1;
@@ -55,10 +53,10 @@
   int _binaryOffsetForStringTable = -1;
   int _binaryOffsetForConstantTable = -1;
 
-  List<CanonicalName> _canonicalNameList;
+  late List<CanonicalName> _canonicalNameList;
   Set<CanonicalName> _knownCanonicalNameNonRootTops = new Set<CanonicalName>();
 
-  Library _currentLibrary;
+  Library? _currentLibrary;
 
   /// Create a printer that writes to the given [sink].
   ///
@@ -66,7 +64,7 @@
   /// one.
   BinaryPrinter(Sink<List<int>> sink,
       {this.libraryFilter,
-      StringIndexer stringIndexer,
+      StringIndexer? stringIndexer,
       this.includeSources = true,
       this.includeOffsets = true})
       : _mainSink = new BufferedSink(sink),
@@ -83,10 +81,9 @@
   }
 
   int _getVariableIndex(VariableDeclaration variable) {
-    _variableIndexer ??= new VariableIndexer();
-    int index = _variableIndexer[variable];
+    int? index = (_variableIndexer ??= new VariableIndexer())[variable];
     assert(index != null, "No index found for ${variable}");
-    return index;
+    return index!;
   }
 
   void writeByte(int byte) {
@@ -132,7 +129,7 @@
     final List<Uint8List> data = <Uint8List>[];
     int totalLength = 0;
     const int minLength = 1 << 16;
-    Uint8List buffer;
+    Uint8List? buffer;
     int index = 0;
 
     // Write the end offsets.
@@ -241,7 +238,7 @@
       constant.typeArguments.forEach(writeDartType);
       writeUInt30(constant.fieldValues.length);
       constant.fieldValues.forEach((Reference fieldRef, Constant value) {
-        writeNonNullCanonicalNameReference(fieldRef.canonicalName);
+        writeNonNullCanonicalNameReference(fieldRef.canonicalName!);
         writeConstantReference(value);
       });
     } else if (constant is PartialInstantiationConstant) {
@@ -254,7 +251,7 @@
       }
     } else if (constant is TearOffConstant) {
       writeByte(ConstantTag.TearOffConstant);
-      writeNonNullCanonicalNameReference(constant.procedure.canonicalName);
+      writeNonNullCanonicalNameReference(constant.procedure.canonicalName!);
     } else if (constant is TypeLiteralConstant) {
       writeByte(ConstantTag.TypeLiteralConstant);
       writeDartType(constant.type);
@@ -273,7 +270,7 @@
   }
 
   // Returns the new active file uri.
-  void writeUriReference(Uri uri) {
+  void writeUriReference(Uri? uri) {
     final int index = _sourceUriIndexer.put(uri);
     writeUInt30(index);
     if (!_currentlyInNonimplementation) {
@@ -478,7 +475,7 @@
     node.accept(this);
   }
 
-  void writeOptionalNode(Node node) {
+  void writeOptionalNode(Node? node) {
     if (node == null) {
       writeByte(Tag.Nothing);
     } else {
@@ -487,7 +484,7 @@
     }
   }
 
-  void writeOptionalFunctionNode(FunctionNode node) {
+  void writeOptionalFunctionNode(FunctionNode? node) {
     if (node == null) {
       writeByte(Tag.Nothing);
     } else {
@@ -505,9 +502,9 @@
     _canonicalNameList = <CanonicalName>[];
     for (int i = 0; i < component.libraries.length; ++i) {
       Library library = component.libraries[i];
-      if (libraryFilter == null || libraryFilter(library)) {
-        _indexLinkTableInternal(library.canonicalName);
-        _knownCanonicalNameNonRootTops.add(library.canonicalName);
+      if (libraryFilter == null || libraryFilter!(library)) {
+        _indexLinkTableInternal(library.canonicalName!);
+        _knownCanonicalNameNonRootTops.add(library.canonicalName!);
       }
     }
   }
@@ -515,7 +512,7 @@
   void _indexLinkTableInternal(CanonicalName node) {
     node.index = _canonicalNameList.length;
     _canonicalNameList.add(node);
-    Iterable<CanonicalName> children = node.childrenOrNull;
+    Iterable<CanonicalName>? children = node.childrenOrNull;
     if (children != null) {
       for (CanonicalName child in children) {
         _indexLinkTableInternal(child);
@@ -527,14 +524,15 @@
   void computeCanonicalNames(Component component) {
     for (int i = 0; i < component.libraries.length; ++i) {
       Library library = component.libraries[i];
-      if (libraryFilter == null || libraryFilter(library)) {
+      if (libraryFilter == null || libraryFilter!(library)) {
         component.computeCanonicalNamesForLibrary(library);
       }
     }
   }
 
   void writeCanonicalNameEntry(CanonicalName node) {
-    CanonicalName parent = node.parent;
+    assert(node.isConsistent, node.getInconsistency());
+    CanonicalName parent = node.parent!;
     if (parent.isRoot) {
       writeUInt30(0);
     } else {
@@ -558,9 +556,9 @@
         _writeNodeMetadataImpl(component, componentOffset);
       }
       libraryOffsets = <int>[];
-      CanonicalName main = getCanonicalNameOfMemberGetter(component.mainMethod);
-      if (main != null) {
-        checkCanonicalName(main);
+      Procedure? mainMethod = component.mainMethod;
+      if (mainMethod != null) {
+        checkCanonicalName(getCanonicalNameOfMemberGetter(mainMethod));
       }
       writeLibraries(component);
       writeUriToSource(component.uriToSource);
@@ -573,7 +571,7 @@
         List<Library> librariesNew = <Library>[];
         for (int i = 0; i < libraries.length; i++) {
           Library library = libraries[i];
-          if (libraryFilter(library)) librariesNew.add(library);
+          if (libraryFilter!(library)) librariesNew.add(library);
         }
         libraries = librariesNew;
       }
@@ -583,7 +581,7 @@
     });
   }
 
-  void writeListOfStrings(List<String> strings) {
+  void writeListOfStrings(List<String>? strings) {
     writeUInt30(strings?.length ?? 0);
     if (strings != null) {
       for (int i = 0; i < strings.length; i++) {
@@ -613,9 +611,9 @@
   }
 
   void _writeNodeMetadataImpl(Node node, int nodeOffset) {
-    for (_MetadataSubsection subsection in _metadataSubsections) {
-      final MetadataRepository<Object> repository = subsection.repository;
-      final Object value = repository.mapping[node];
+    for (_MetadataSubsection subsection in _metadataSubsections!) {
+      final MetadataRepository<Object?> repository = subsection.repository;
+      final Object? value = repository.mapping[node];
       if (value == null) {
         continue;
       }
@@ -641,7 +639,7 @@
 
   @override
   void enterScope(
-      {List<TypeParameter> typeParameters,
+      {List<TypeParameter>? typeParameters,
       bool memberScope: false,
       bool variableScope: false}) {
     if (typeParameters != null) {
@@ -652,17 +650,17 @@
     }
     if (variableScope) {
       _variableIndexer ??= new VariableIndexer();
-      _variableIndexer.pushScope();
+      _variableIndexer!.pushScope();
     }
   }
 
   @override
   void leaveScope(
-      {List<TypeParameter> typeParameters,
+      {List<TypeParameter>? typeParameters,
       bool memberScope: false,
       bool variableScope: false}) {
     if (variableScope) {
-      _variableIndexer.popScope();
+      _variableIndexer!.popScope();
     }
     if (memberScope) {
       _variableIndexer = null;
@@ -687,7 +685,7 @@
     _metadataSubsections
         ?.removeWhere((_MetadataSubsection s) => s.metadataMapping.isEmpty);
 
-    if (_metadataSubsections == null || _metadataSubsections.isEmpty) {
+    if (_metadataSubsections == null || _metadataSubsections!.isEmpty) {
       _binaryOffsetForMetadataMappings = getBufferOffset();
       writeUInt32(0); // Empty section.
       return;
@@ -699,7 +697,7 @@
 
     // RList<MetadataMapping> metadataMappings
     _binaryOffsetForMetadataMappings = getBufferOffset();
-    for (_MetadataSubsection subsection in _metadataSubsections) {
+    for (_MetadataSubsection subsection in _metadataSubsections!) {
       // UInt32 tag
       writeUInt32(stringIndexer.put(subsection.repository.tag));
 
@@ -711,14 +709,14 @@
       }
       writeUInt32(mappingLength ~/ 2);
     }
-    writeUInt32(_metadataSubsections.length);
+    writeUInt32(_metadataSubsections!.length);
   }
 
   /// Write all of some of the libraries of the [component].
   void writeLibraries(Component component) {
     for (int i = 0; i < component.libraries.length; ++i) {
       Library library = component.libraries[i];
-      if (libraryFilter == null || libraryFilter(library)) {
+      if (libraryFilter == null || libraryFilter!(library)) {
         writeLibraryNode(library);
       }
     }
@@ -758,10 +756,11 @@
     assert(_binaryOffsetForConstantTable >= 0);
     writeUInt32(_binaryOffsetForConstantTable);
 
-    CanonicalName main = getCanonicalNameOfMemberGetter(component.mainMethod);
-    if (main == null) {
+    Procedure? mainMethod = component.mainMethod;
+    if (mainMethod == null) {
       writeUInt32(0);
     } else {
+      CanonicalName main = getCanonicalNameOfMemberGetter(mainMethod);
       writeUInt32(main.index + 1);
     }
     assert(component.modeRaw != null, "Component mode not set.");
@@ -782,14 +781,17 @@
 
     int length = _sourceUriIndexer.index.length;
     writeUInt32(length);
-    List<int> index = new List<int>.filled(length, null);
+    List<int> index = new List<int>.filled(
+        length,
+        // Dummy element value.
+        -1);
 
     // Write data.
     int i = 0;
     Uint8List buffer = new Uint8List(1 << 16);
-    for (Uri uri in _sourceUriIndexer.index.keys) {
+    for (Uri? uri in _sourceUriIndexer.index.keys) {
       index[i] = getBufferOffset();
-      Source source = uriToSource[uri];
+      Source? source = uriToSource[uri];
       if (source == null ||
           !(includeSources &&
               _sourcesFromRealImplementation.length > i &&
@@ -804,7 +806,7 @@
       writeByteList(source.source);
 
       {
-        List<int> lineStarts = source.lineStarts;
+        List<int> lineStarts = source.lineStarts!;
         writeUInt30(lineStarts.length);
         int previousLineStart = 0;
         for (int j = 0; j < lineStarts.length; ++j) {
@@ -819,7 +821,7 @@
       outputStringViaBuffer(importUriAsString, buffer);
 
       {
-        Set<Reference> coverage = source.constantCoverageConstructors;
+        Set<Reference>? coverage = source.constantCoverageConstructors;
         if (coverage == null || coverage.isEmpty) {
           writeUInt30(0);
         } else {
@@ -853,7 +855,7 @@
   }
 
   void writeLibraryDependencyReference(LibraryDependency node) {
-    int index = _libraryDependencyIndex[node];
+    int? index = _libraryDependencyIndex[node];
     if (index == null) {
       throw new ArgumentError(
           'Reference to library dependency $node out of scope');
@@ -861,17 +863,18 @@
     writeUInt30(index);
   }
 
-  void writeNullAllowedInstanceMemberReference(Reference reference) {
+  void writeNullAllowedInstanceMemberReference(Reference? reference) {
     writeNullAllowedReference(reference);
     writeNullAllowedReference(
-        getMemberReferenceGetter(reference?.asMember?.memberSignatureOrigin));
+        getMemberReferenceGetter(reference?.asMember.memberSignatureOrigin));
   }
 
-  void writeNullAllowedReference(Reference reference) {
+  void writeNullAllowedReference(Reference? reference) {
     if (reference == null) {
       writeUInt30(0);
     } else {
-      CanonicalName name = reference.canonicalName;
+      assert(reference.isConsistent, reference.getInconsistency());
+      CanonicalName? name = reference.canonicalName;
       if (name == null) {
         throw new ArgumentError('Missing canonical name for $reference');
       }
@@ -883,14 +886,16 @@
   void writeNonNullInstanceMemberReference(Reference reference) {
     writeNonNullReference(reference);
     writeNullAllowedReference(
-        getMemberReferenceGetter(reference.asMember?.memberSignatureOrigin));
+        getMemberReferenceGetter(reference.asMember.memberSignatureOrigin));
   }
 
   void writeNonNullReference(Reference reference) {
+    // ignore: unnecessary_null_comparison
     if (reference == null) {
       throw new ArgumentError('Got null reference');
     } else {
-      CanonicalName name = reference.canonicalName;
+      assert(reference.isConsistent, reference.getInconsistency());
+      CanonicalName? name = reference.canonicalName;
       if (name == null) {
         throw new ArgumentError('Missing canonical name for $reference');
       }
@@ -901,7 +906,7 @@
 
   void checkCanonicalName(CanonicalName node) {
     if (_knownCanonicalNameNonRootTops.contains(node.nonRootTop)) return;
-    if (node == null || node.isRoot) return;
+    if (node.isRoot) return;
     if (node.index >= 0 && node.index < _canonicalNameList.length) {
       CanonicalName claim = _canonicalNameList[node.index];
       if (node == claim) {
@@ -909,12 +914,12 @@
         return;
       }
     }
-    checkCanonicalName(node.parent);
+    checkCanonicalName(node.parent!);
     node.index = _canonicalNameList.length;
     _canonicalNameList.add(node);
   }
 
-  void writeNullAllowedCanonicalNameReference(CanonicalName name) {
+  void writeNullAllowedCanonicalNameReference(CanonicalName? name) {
     if (name == null) {
       writeUInt30(0);
     } else {
@@ -924,6 +929,7 @@
   }
 
   void writeNonNullCanonicalNameReference(CanonicalName name) {
+    // ignore: unnecessary_null_comparison
     if (name == null) {
       throw new ArgumentError(
           'Expected a canonical name to be valid but was `null`.');
@@ -953,6 +959,7 @@
   }
 
   void writeClassReference(Class class_) {
+    // ignore: unnecessary_null_comparison
     if (class_ == null) {
       throw new ArgumentError(
           'Expected a class reference to be valid but was `null`.');
@@ -968,7 +975,7 @@
     // TODO: Consider a more compressed format for private names within the
     // enclosing library.
     if (node.isPrivate) {
-      writeLibraryReference(node.library);
+      writeLibraryReference(node.library!);
     }
   }
 
@@ -1111,7 +1118,7 @@
 
     enterScope(typeParameters: node.typeParameters, variableScope: true);
     writeNodeList(node.typeParameters);
-    writeNode(node.type);
+    writeNode(node.type!);
 
     enterScope(typeParameters: node.typeParametersOfFunctionType);
     writeNodeList(node.typeParametersOfFunctionType);
@@ -1153,7 +1160,7 @@
     writeOffset(node.fileEndOffset);
 
     writeByte(node.flags);
-    writeStringReference(node.name ?? '');
+    writeStringReference(node.name);
 
     enterScope(memberScope: true);
     writeAnnotationList(node.annotations);
@@ -1200,12 +1207,12 @@
     writeName(node.name ?? _emptyName);
 
     writeAnnotationList(node.annotations);
-    assert(node.function.typeParameters.isEmpty);
-    writeFunctionNode(node.function);
+    assert(node.function!.typeParameters.isEmpty);
+    writeFunctionNode(node.function!);
     // Parameters are in scope in the initializers.
     _variableIndexer ??= new VariableIndexer();
-    _variableIndexer.restoreScope(node.function.positionalParameters.length +
-        node.function.namedParameters.length);
+    _variableIndexer!.restoreScope(node.function!.positionalParameters.length +
+        node.function!.namedParameters.length);
     writeNodeList(node.initializers);
 
     leaveScope(memberScope: true);
@@ -1229,6 +1236,22 @@
     if (node.canonicalName == null) {
       throw new ArgumentError('Missing canonical name for $node');
     }
+    if (node.reference.node != node) {
+      throw new ArgumentError(
+          'Trying to serialize orphaned procedure reference.\n'
+          'Orphaned procedure ${node} (${node.runtimeType}:${node.hashCode})\n'
+          'Linked node ${node.reference.node} '
+          '(${node.reference.node.runtimeType}:'
+          '${node.reference.node.hashCode})');
+    }
+    if (node.canonicalName!.reference?.node != node) {
+      throw new ArgumentError(
+          'Trying to serialize orphaned procedure canonical name.\n'
+          'Orphaned procedure ${node} (${node.runtimeType}:${node.hashCode})\n'
+          'Linked node ${node.canonicalName!.reference?.node} '
+          '(${node.canonicalName!.reference?.node.runtimeType}:'
+          '${node.canonicalName!.reference?.node.hashCode})');
+    }
 
     final bool currentlyInNonimplementationSaved =
         _currentlyInNonimplementation;
@@ -1255,7 +1278,7 @@
     _currentlyInNonimplementation = currentlyInNonimplementationSaved;
     assert(
         (node.concreteForwardingStubTarget != null) ||
-            !(node.isForwardingStub && node.function.body != null),
+            !(node.isForwardingStub && node.function!.body != null),
         "Invalid forwarding stub $node.");
   }
 
@@ -1264,8 +1287,42 @@
     if (node.getterCanonicalName == null) {
       throw new ArgumentError('Missing canonical name for $node');
     }
-    if (node.hasSetter && node.setterCanonicalName == null) {
-      throw new ArgumentError('Missing canonical name for $node');
+    if (node.getterReference.node != node) {
+      throw new ArgumentError('Trying to serialize orphaned getter reference.\n'
+          'Orphaned getter ${node} (${node.runtimeType}:${node.hashCode})\n'
+          'Linked node ${node.getterReference.node} '
+          '(${node.getterReference.node.runtimeType}:'
+          '${node.getterReference.node.hashCode})');
+    }
+    if (node.getterCanonicalName!.reference?.node != node) {
+      throw new ArgumentError(
+          'Trying to serialize orphaned getter canonical name.\n'
+          'Orphaned getter ${node} '
+          '(${node.runtimeType}:${node.hashCode})\n'
+          'Linked node ${node.getterCanonicalName!.reference?.node} '
+          '(${node.getterCanonicalName!.reference?.node.runtimeType}:'
+          '${node.getterCanonicalName!.reference?.node.hashCode})');
+    }
+    if (node.hasSetter) {
+      if (node.setterCanonicalName == null) {
+        throw new ArgumentError('Missing canonical name for $node');
+      }
+      if (node.setterReference?.node != node) {
+        throw new ArgumentError(
+            'Trying to serialize orphaned setter reference.\n'
+            'Orphaned setter ${node} (${node.runtimeType}:${node.hashCode})\n'
+            'Linked node ${node.setterReference?.node}'
+            '(${node.setterReference?.node.runtimeType}:'
+            '${node.setterReference?.node.hashCode})');
+      }
+      if (node.setterCanonicalName!.reference?.node != node) {
+        throw new ArgumentError(
+            'Trying to serialize orphaned setter canonical name.\n'
+            'Orphaned setter ${node} (${node.runtimeType}:${node.hashCode})\n'
+            'Linked node ${node.setterCanonicalName!.reference?.node} '
+            '(${node.setterCanonicalName!.reference?.node.runtimeType}:'
+            '${node.setterCanonicalName!.reference?.node.hashCode})');
+      }
     }
     enterScope(memberScope: true);
     writeByte(Tag.Field);
@@ -1279,7 +1336,7 @@
     writeOffset(node.fileOffset);
     writeOffset(node.fileEndOffset);
     writeUInt30(node.flags);
-    writeName(node.name);
+    writeName(node.name!);
     writeAnnotationList(node.annotations);
     writeNode(node.type);
     writeOptionalNode(node.initializer);
@@ -1301,10 +1358,10 @@
     writeOffset(node.fileOffset);
     writeOffset(node.fileEndOffset);
     writeByte(node.flags);
-    writeName(node.name);
+    writeName(node.name!);
 
     writeAnnotationList(node.annotations);
-    writeNonNullReference(node.targetReference);
+    writeNonNullReference(node.targetReference!);
     writeNodeList(node.typeArguments);
     writeNodeList(node.typeParameters);
     writeUInt30(node.positionalParameters.length + node.namedParameters.length);
@@ -1368,9 +1425,9 @@
   void visitFunctionNode(FunctionNode node) {
     writeByte(Tag.FunctionNode);
     enterScope(typeParameters: node.typeParameters, variableScope: true);
-    LabelIndexer oldLabels = _labelIndexer;
+    LabelIndexer? oldLabels = _labelIndexer;
     _labelIndexer = null;
-    SwitchCaseIndexer oldCases = _switchCaseIndexer;
+    SwitchCaseIndexer? oldCases = _switchCaseIndexer;
     _switchCaseIndexer = null;
     // Note: FunctionNode has no tag.
     writeOffset(node.fileOffset);
@@ -1688,7 +1745,6 @@
       case LogicalExpressionOperator.OR:
         return 1;
     }
-    throw new ArgumentError('Not a logical operator: $operator');
   }
 
   @override
@@ -1911,21 +1967,23 @@
   void visitLet(Let node) {
     writeByte(Tag.Let);
     writeOffset(node.fileOffset);
-    _variableIndexer ??= new VariableIndexer();
-    _variableIndexer.pushScope();
+    VariableIndexer variableIndexer =
+        _variableIndexer ??= new VariableIndexer();
+    variableIndexer.pushScope();
     writeVariableDeclaration(node.variable);
     writeNode(node.body);
-    _variableIndexer.popScope();
+    variableIndexer.popScope();
   }
 
   @override
   void visitBlockExpression(BlockExpression node) {
     writeByte(Tag.BlockExpression);
-    _variableIndexer ??= new VariableIndexer();
-    _variableIndexer.pushScope();
+    VariableIndexer variableIndexer =
+        _variableIndexer ??= new VariableIndexer();
+    variableIndexer.pushScope();
     writeNodeList(node.body.statements);
     writeNode(node.value);
-    _variableIndexer.popScope();
+    variableIndexer.popScope();
   }
 
   @override
@@ -1947,7 +2005,7 @@
     writeLibraryDependencyReference(node.import);
   }
 
-  writeStatementOrEmpty(Statement node) {
+  writeStatementOrEmpty(Statement? node) {
     if (node == null) {
       writeByte(Tag.EmptyStatement);
     } else {
@@ -1963,22 +2021,24 @@
 
   @override
   void visitBlock(Block node) {
-    _variableIndexer ??= new VariableIndexer();
-    _variableIndexer.pushScope();
+    VariableIndexer variableIndexer =
+        _variableIndexer ??= new VariableIndexer();
+    variableIndexer.pushScope();
     writeByte(Tag.Block);
     writeOffset(node.fileOffset);
     writeOffset(node.fileEndOffset);
     writeNodeList(node.statements);
-    _variableIndexer.popScope();
+    variableIndexer.popScope();
   }
 
   @override
   void visitAssertBlock(AssertBlock node) {
-    _variableIndexer ??= new VariableIndexer();
-    _variableIndexer.pushScope();
+    VariableIndexer variableIndexer =
+        _variableIndexer ??= new VariableIndexer();
+    variableIndexer.pushScope();
     writeByte(Tag.AssertBlock);
     writeNodeList(node.statements);
-    _variableIndexer.popScope();
+    variableIndexer.popScope();
   }
 
   @override
@@ -1997,13 +2057,11 @@
 
   @override
   void visitLabeledStatement(LabeledStatement node) {
-    if (_labelIndexer == null) {
-      _labelIndexer = new LabelIndexer();
-    }
-    _labelIndexer.enter(node);
+    LabelIndexer labelIndexer = _labelIndexer ??= new LabelIndexer();
+    labelIndexer.enter(node);
     writeByte(Tag.LabeledStatement);
     writeNode(node.body);
-    _labelIndexer.exit();
+    labelIndexer.exit();
   }
 
   @override
@@ -2018,7 +2076,7 @@
   void visitBreakStatement(BreakStatement node) {
     writeByte(Tag.BreakStatement);
     writeOffset(node.fileOffset);
-    writeUInt30(_labelIndexer[node.target]);
+    writeUInt30(_labelIndexer![node.target]!);
   }
 
   @override
@@ -2039,41 +2097,42 @@
 
   @override
   void visitForStatement(ForStatement node) {
-    _variableIndexer ??= new VariableIndexer();
-    _variableIndexer.pushScope();
+    VariableIndexer variableIndexer =
+        _variableIndexer ??= new VariableIndexer();
+    variableIndexer.pushScope();
     writeByte(Tag.ForStatement);
     writeOffset(node.fileOffset);
     writeVariableDeclarationList(node.variables);
     writeOptionalNode(node.condition);
     writeNodeList(node.updates);
     writeNode(node.body);
-    _variableIndexer.popScope();
+    variableIndexer.popScope();
   }
 
   @override
   void visitForInStatement(ForInStatement node) {
-    _variableIndexer ??= new VariableIndexer();
-    _variableIndexer.pushScope();
+    VariableIndexer variableIndexer =
+        _variableIndexer ??= new VariableIndexer();
+    variableIndexer.pushScope();
     writeByte(node.isAsync ? Tag.AsyncForInStatement : Tag.ForInStatement);
     writeOffset(node.fileOffset);
     writeOffset(node.bodyOffset);
     writeVariableDeclaration(node.variable);
     writeNode(node.iterable);
     writeNode(node.body);
-    _variableIndexer.popScope();
+    variableIndexer.popScope();
   }
 
   @override
   void visitSwitchStatement(SwitchStatement node) {
-    if (_switchCaseIndexer == null) {
-      _switchCaseIndexer = new SwitchCaseIndexer();
-    }
-    _switchCaseIndexer.enter(node);
+    SwitchCaseIndexer switchCaseIndexer =
+        _switchCaseIndexer ??= new SwitchCaseIndexer();
+    switchCaseIndexer.enter(node);
     writeByte(Tag.SwitchStatement);
     writeOffset(node.fileOffset);
     writeNode(node.expression);
     writeSwitchCaseNodeList(node.cases);
-    _switchCaseIndexer.exit(node);
+    switchCaseIndexer.exit(node);
   }
 
   @override
@@ -2093,7 +2152,7 @@
   void visitContinueSwitchStatement(ContinueSwitchStatement node) {
     writeByte(Tag.ContinueSwitchStatement);
     writeOffset(node.fileOffset);
-    writeUInt30(_switchCaseIndexer[node.target]);
+    writeUInt30(_switchCaseIndexer![node.target]!);
   }
 
   @override
@@ -2128,14 +2187,15 @@
   @override
   void visitCatch(Catch node) {
     // Note: there is no tag on Catch.
-    _variableIndexer ??= new VariableIndexer();
-    _variableIndexer.pushScope();
+    VariableIndexer variableIndexer =
+        _variableIndexer ??= new VariableIndexer();
+    variableIndexer.pushScope();
     writeOffset(node.fileOffset);
     writeNode(node.guard);
     writeOptionalVariableDeclaration(node.exception);
     writeOptionalVariableDeclaration(node.stackTrace);
     writeNode(node.body);
-    _variableIndexer.popScope();
+    variableIndexer.popScope();
   }
 
   @override
@@ -2173,15 +2233,14 @@
     writeOptionalNode(node.initializer);
     // Declare the variable after its initializer. It is not in scope in its
     // own initializer.
-    _variableIndexer ??= new VariableIndexer();
-    _variableIndexer.declare(node);
+    (_variableIndexer ??= new VariableIndexer()).declare(node);
   }
 
   void writeVariableDeclarationList(List<VariableDeclaration> nodes) {
     writeList(nodes, writeVariableDeclaration);
   }
 
-  void writeOptionalVariableDeclaration(VariableDeclaration node) {
+  void writeOptionalVariableDeclaration(VariableDeclaration? node) {
     if (node == null) {
       writeByte(Tag.Nothing);
     } else {
@@ -2195,7 +2254,7 @@
     writeByte(Tag.FunctionDeclaration);
     writeOffset(node.fileOffset);
     writeVariableDeclaration(node.variable);
-    writeFunctionNode(node.function);
+    writeFunctionNode(node.function!);
   }
 
   @override
@@ -2237,11 +2296,10 @@
   void visitFutureOrType(FutureOrType node) {
     // TODO(dmitryas): Remove special treatment of FutureOr when the VM supports
     // the new encoding: just write the tag.
-    assert(_knownCanonicalNameNonRootTops != null &&
-        _knownCanonicalNameNonRootTops.isNotEmpty);
+    assert(_knownCanonicalNameNonRootTops.isNotEmpty);
     CanonicalName root = _knownCanonicalNameNonRootTops.first;
     while (!root.isRoot) {
-      root = root.parent;
+      root = root.parent!;
     }
     CanonicalName canonicalNameOfFutureOr =
         root.getChild("dart:async").getChild("FutureOr");
@@ -2257,11 +2315,10 @@
   void visitNullType(NullType node) {
     // TODO(dmitryas): Remove special treatment of Null when the VM supports the
     // new encoding: just write the tag.
-    assert(_knownCanonicalNameNonRootTops != null &&
-        _knownCanonicalNameNonRootTops.isNotEmpty);
+    assert(_knownCanonicalNameNonRootTops.isNotEmpty);
     CanonicalName root = _knownCanonicalNameNonRootTops.first;
     while (!root.isRoot) {
-      root = root.parent;
+      root = root.parent!;
     }
     CanonicalName canonicalNameOfNull =
         root.getChild("dart:core").getChild("Null");
@@ -2279,11 +2336,11 @@
     // requires the nullability byte.
     if (node.typeArguments.isEmpty) {
       writeByte(Tag.SimpleInterfaceType);
-      writeByte(_currentLibrary.nonNullable.index);
+      writeByte(_currentLibrary!.nonNullable.index);
       writeNonNullReference(node.className);
     } else {
       writeByte(Tag.InterfaceType);
-      writeByte(_currentLibrary.nonNullable.index);
+      writeByte(_currentLibrary!.nonNullable.index);
       writeNonNullReference(node.className);
       writeNodeList(node.typeArguments);
     }
@@ -2349,7 +2406,8 @@
       writeByte(node.variance);
     }
     writeStringReference(node.name ?? '');
-    writeNode(node.bound);
+    writeNode(node.bound!);
+    // TODO(johnniwinther): Make this non-optional.
     writeOptionalNode(node.defaultType);
   }
 
@@ -2360,7 +2418,7 @@
     }
     writeByte(Tag.Extension);
     writeNonNullCanonicalNameReference(getCanonicalNameOfExtension(node));
-    writeStringReference(node.name ?? '');
+    writeStringReference(node.name);
     writeUriReference(node.fileUri);
     writeOffset(node.fileOffset);
 
@@ -2376,7 +2434,7 @@
       writeName(descriptor.name);
       writeByte(descriptor.kind.index);
       writeByte(descriptor.flags);
-      writeNonNullCanonicalNameReference(descriptor.member.canonicalName);
+      writeNonNullCanonicalNameReference(descriptor.member.canonicalName!);
     }
   }
 
@@ -2657,30 +2715,28 @@
 typedef bool LibraryFilter(Library _);
 
 class VariableIndexer {
-  Map<VariableDeclaration, int> index;
-  List<int> scopes;
+  Map<VariableDeclaration, int>? index;
+  List<int>? scopes;
   int stackHeight = 0;
 
   void declare(VariableDeclaration node) {
-    index ??= <VariableDeclaration, int>{};
-    index[node] = stackHeight++;
+    (index ??= <VariableDeclaration, int>{})[node] = stackHeight++;
   }
 
   void pushScope() {
-    scopes ??= <int>[];
-    scopes.add(stackHeight);
+    (scopes ??= <int>[]).add(stackHeight);
   }
 
   void popScope() {
-    stackHeight = scopes.removeLast();
+    stackHeight = scopes!.removeLast();
   }
 
   void restoreScope(int numberOfVariables) {
     stackHeight += numberOfVariables;
   }
 
-  int operator [](VariableDeclaration node) {
-    return index == null ? null : index[node];
+  int? operator [](VariableDeclaration node) {
+    return index == null ? null : index![node];
   }
 }
 
@@ -2696,7 +2752,7 @@
     --stackHeight;
   }
 
-  int operator [](LabeledStatement node) => index[node];
+  int? operator [](LabeledStatement node) => index[node];
 }
 
 class SwitchCaseIndexer {
@@ -2713,7 +2769,7 @@
     stackHeight -= node.cases.length;
   }
 
-  int operator [](SwitchCase node) => index[node];
+  int? operator [](SwitchCase node) => index[node];
 }
 
 class ConstantIndexer extends RecursiveResultVisitor {
@@ -2728,7 +2784,7 @@
   ConstantIndexer(this.stringIndexer, this._printer);
 
   int put(Constant constant) {
-    final int oldOffset = offsets[constant];
+    final int? oldOffset = offsets[constant];
     if (oldOffset != null) return oldOffset;
 
     // Traverse DAG in post-order to ensure children have their offsets assigned
@@ -2758,7 +2814,7 @@
     put(node);
   }
 
-  int operator [](Constant node) => offsets[node];
+  int? operator [](Constant node) => offsets[node];
 }
 
 class TypeParameterIndexer {
@@ -2794,7 +2850,7 @@
   }
 
   int put(String string) {
-    int result = index[string];
+    int? result = index[string];
     if (result == null) {
       result = index.length;
       index[string] = result;
@@ -2802,19 +2858,19 @@
     return result;
   }
 
-  int operator [](String string) => index[string];
+  int? operator [](String string) => index[string];
 }
 
 class UriIndexer {
   // Note that the iteration order is important.
-  final Map<Uri, int> index = new Map<Uri, int>();
+  final Map<Uri?, int> index = new Map<Uri?, int>();
 
   UriIndexer() {
     put(null);
   }
 
-  int put(Uri uri) {
-    int result = index[uri];
+  int put(Uri? uri) {
+    int? result = index[uri];
     if (result == null) {
       result = index.length;
       index[uri] = result;
@@ -2834,19 +2890,20 @@
   int flushedLength = 0;
 
   Float64List _doubleBuffer = new Float64List(1);
-  Uint8List _doubleBufferUint8;
+  Uint8List? _doubleBufferUint8;
 
   int get offset => length + flushedLength;
 
   BufferedSink(this._sink);
 
   void addDouble(double d) {
-    _doubleBufferUint8 ??= _doubleBuffer.buffer.asUint8List();
+    Uint8List doubleBufferUint8 =
+        _doubleBufferUint8 ??= _doubleBuffer.buffer.asUint8List();
     _doubleBuffer[0] = d;
-    addByte4(_doubleBufferUint8[0], _doubleBufferUint8[1],
-        _doubleBufferUint8[2], _doubleBufferUint8[3]);
-    addByte4(_doubleBufferUint8[4], _doubleBufferUint8[5],
-        _doubleBufferUint8[6], _doubleBufferUint8[7]);
+    addByte4(doubleBufferUint8[0], doubleBufferUint8[1], doubleBufferUint8[2],
+        doubleBufferUint8[3]);
+    addByte4(doubleBufferUint8[4], doubleBufferUint8[5], doubleBufferUint8[6],
+        doubleBufferUint8[7]);
   }
 
   @pragma("vm:prefer-inline")
@@ -2926,7 +2983,7 @@
 
 /// Non-empty metadata subsection.
 class _MetadataSubsection {
-  final MetadataRepository<Object> repository;
+  final MetadataRepository<Object?> repository;
 
   /// List of (nodeOffset, metadataOffset) pairs.
   /// Gradually filled by the writer as writing progresses, which by
diff --git a/pkg/kernel/lib/binary/tag.dart b/pkg/kernel/lib/binary/tag.dart
index 3b8d1ee..7047f59 100644
--- a/pkg/kernel/lib/binary/tag.dart
+++ b/pkg/kernel/lib/binary/tag.dart
@@ -174,7 +174,7 @@
   /// Internal version of kernel binary format.
   /// Bump it when making incompatible changes in kernel binaries.
   /// Keep in sync with runtime/vm/kernel_binary.h, pkg/kernel/binary.md.
-  static const int BinaryFormatVersion = 57;
+  static const int BinaryFormatVersion = 58;
 }
 
 abstract class ConstantTag {
diff --git a/pkg/kernel/lib/canonical_name.dart b/pkg/kernel/lib/canonical_name.dart
index af7344e..cfaa67f 100644
--- a/pkg/kernel/lib/canonical_name.dart
+++ b/pkg/kernel/lib/canonical_name.dart
@@ -30,9 +30,14 @@
 ///         "@constructors"
 ///         Qualified name
 ///
-///      Field:
+///      Field or the implicit getter of a field:
 ///         Canonical name of enclosing class or library
-///         "@fields"
+///         "@getters"
+///         Qualified name
+///
+///      Implicit setter of a field:
+///         Canonical name of enclosing class or library
+///         "@setters"
 ///         Qualified name
 ///
 ///      Typedef:
@@ -132,11 +137,11 @@
   }
 
   CanonicalName getChildFromField(Field field) {
-    return getChild('@fields').getChildFromQualifiedName(field.name!);
+    return getChild('@getters').getChildFromQualifiedName(field.name!);
   }
 
   CanonicalName getChildFromFieldSetter(Field field) {
-    return getChild('@=fields').getChildFromQualifiedName(field.name!);
+    return getChild('@setters').getChildFromQualifiedName(field.name!);
   }
 
   CanonicalName getChildFromConstructor(Constructor constructor) {
@@ -151,11 +156,11 @@
   }
 
   CanonicalName getChildFromFieldWithName(Name name) {
-    return getChild('@fields').getChildFromQualifiedName(name);
+    return getChild('@getters').getChildFromQualifiedName(name);
   }
 
   CanonicalName getChildFromFieldSetterWithName(Name name) {
-    return getChild('@=fields').getChildFromQualifiedName(name);
+    return getChild('@setters').getChildFromQualifiedName(name);
   }
 
   CanonicalName getChildFromTypedef(Typedef typedef_) {
@@ -256,6 +261,22 @@
     return reference ??= (new Reference()..canonicalName = this);
   }
 
+  bool get isConsistent {
+    if (reference != null && !reference!.isConsistent) {
+      return false;
+    }
+    return true;
+  }
+
+  String getInconsistency() {
+    StringBuffer sb = new StringBuffer();
+    sb.write('CanonicalName ${this} (${hashCode}):');
+    if (reference != null) {
+      sb.write(' ${reference!.getInconsistency()}');
+    }
+    return sb.toString();
+  }
+
   static String getProcedureQualifier(Procedure procedure) {
     if (procedure.isGetter) return '@getters';
     if (procedure.isSetter) return '@setters';
diff --git a/pkg/kernel/test/convert_field_to_setter_getter.dart b/pkg/kernel/test/convert_field_to_setter_getter.dart
index 8cc17a4..1ae22d8 100644
--- a/pkg/kernel/test/convert_field_to_setter_getter.dart
+++ b/pkg/kernel/test/convert_field_to_setter_getter.dart
@@ -37,12 +37,14 @@
   List<int> writtenBytesFieldOriginal = serialize(lib1, lib2);
   // Canonical names are now set: Verify that the field is marked as such,
   // canonical-name-wise.
-  if (field.getterCanonicalName.parent.name != "@fields") {
-    throw "Expected @fields parent, but had "
+  String getterCanonicalName = '${field.getterCanonicalName}';
+  if (field.getterCanonicalName.parent.name != "@getters") {
+    throw "Expected @getters parent, but had "
         "${field.getterCanonicalName.parent.name}";
   }
-  if (field.setterCanonicalName.parent.name != "@=fields") {
-    throw "Expected @=fields parent, but had "
+  String setterCanonicalName = '${field.setterCanonicalName}';
+  if (field.setterCanonicalName.parent.name != "@setters") {
+    throw "Expected @setters parent, but had "
         "${field.setterCanonicalName.parent.name}";
   }
 
@@ -80,10 +82,18 @@
     throw "Expected @getters parent, but had "
         "${getter.canonicalName.parent.name}";
   }
+  if ('${getter.canonicalName}' != getterCanonicalName) {
+    throw "Unexpected getter canonical name. "
+        "Expected $getterCanonicalName, actual ${getter.canonicalName}.";
+  }
   if (setter.canonicalName.parent.name != "@setters") {
     throw "Expected @setters parent, but had "
         "${setter.canonicalName.parent.name}";
   }
+  if ('${setter.canonicalName}' != setterCanonicalName) {
+    throw "Unexpected setter canonical name. "
+        "Expected $setterCanonicalName, actual ${setter.canonicalName}.";
+  }
 
   // Replace getter/setter with field.
   lib1.procedures.remove(getter);
@@ -101,12 +111,12 @@
   List<int> writtenBytesFieldNew = serialize(lib1, lib2);
   // Canonical names are now set: Verify that the field is marked as such,
   // canonical-name-wise.
-  if (fieldReplacement.getterCanonicalName.parent.name != "@fields") {
-    throw "Expected @fields parent, but had "
+  if (fieldReplacement.getterCanonicalName.parent.name != "@getters") {
+    throw "Expected @getters parent, but had "
         "${fieldReplacement.getterCanonicalName.parent.name}";
   }
-  if (fieldReplacement.setterCanonicalName.parent.name != "@=fields") {
-    throw "Expected @=fields parent, but had "
+  if (fieldReplacement.setterCanonicalName.parent.name != "@setters") {
+    throw "Expected @setters parent, but had "
         "${fieldReplacement.setterCanonicalName.parent.name}";
   }
 
diff --git a/pkg/kernel/test/text_serializer_from_kernel_nodes_test.dart b/pkg/kernel/test/text_serializer_from_kernel_nodes_test.dart
index 0c1c2e4a..df7b9ff 100644
--- a/pkg/kernel/test/text_serializer_from_kernel_nodes_test.dart
+++ b/pkg/kernel/test/text_serializer_from_kernel_nodes_test.dart
@@ -133,7 +133,7 @@
           name: '/* suppose top-level: dynamic field; */ field;',
           node: new ExpressionStatement(new StaticGet(field)),
           expectation: ''
-              '(expr (get-static "package:foo/bar.dart::@fields::field"))',
+              '(expr (get-static "package:foo/bar.dart::@getters::field"))',
           makeSerializationState: () => new SerializationState(null),
           makeDeserializationState: () =>
               new DeserializationState(null, component.root),
@@ -151,7 +151,7 @@
           name: '/* suppose top-level: dynamic field; */ field;',
           node: new ExpressionStatement(new StaticGet(field)),
           expectation: ''
-              '(expr (get-static "package:foo/bar.dart::@fields::field"))',
+              '(expr (get-static "package:foo/bar.dart::@getters::field"))',
           makeSerializationState: () => new SerializationState(null),
           makeDeserializationState: () =>
               new DeserializationState(null, component.root),
@@ -171,7 +171,7 @@
               new ExpressionStatement(new StaticSet(field, new IntLiteral(1))),
           expectation: ''
               '(expr'
-              ' (set-static "package:foo/bar.dart::@=fields::field" (int 1)))',
+              ' (set-static "package:foo/bar.dart::@setters::field" (int 1)))',
           makeSerializationState: () => new SerializationState(null),
           makeDeserializationState: () =>
               new DeserializationState(null, component.root),
diff --git a/pkg/vm/lib/metadata/inferred_type.dart b/pkg/vm/lib/metadata/inferred_type.dart
index 5f0c9af..e88f513 100644
--- a/pkg/vm/lib/metadata/inferred_type.dart
+++ b/pkg/vm/lib/metadata/inferred_type.dart
@@ -114,8 +114,9 @@
   void writeToBinary(InferredType metadata, Node node, BinarySink sink) {
     // TODO(sjindel/tfa): Implement serialization of type arguments when can use
     // them for optimizations.
-    sink.writeNullAllowedCanonicalNameReference(
-        getCanonicalNameOfClass(metadata.concreteClass));
+    sink.writeNullAllowedCanonicalNameReference(metadata.concreteClass != null
+        ? getCanonicalNameOfClass(metadata.concreteClass)
+        : null);
     sink.writeByte(metadata._flags);
     if (metadata.constantValue != null) {
       sink.writeConstantReference(metadata.constantValue);
diff --git a/pkg/vm/lib/transformations/ffi.dart b/pkg/vm/lib/transformations/ffi.dart
index 449b5b0..7f1d707 100644
--- a/pkg/vm/lib/transformations/ffi.dart
+++ b/pkg/vm/lib/transformations/ffi.dart
@@ -7,13 +7,14 @@
 
 library vm.transformations.ffi;
 
-import 'package:kernel/ast.dart';
+import 'package:kernel/ast.dart' hide MapEntry;
 import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
 import 'package:kernel/core_types.dart';
 import 'package:kernel/library_index.dart' show LibraryIndex;
 import 'package:kernel/reference_from_index.dart';
 import 'package:kernel/target/targets.dart' show DiagnosticReporter;
-import 'package:kernel/type_environment.dart' show TypeEnvironment;
+import 'package:kernel/type_environment.dart'
+    show TypeEnvironment, SubtypeCheckMode;
 
 /// Represents the (instantiated) ffi.NativeType.
 enum NativeType {
@@ -212,8 +213,14 @@
   final Class allocatorClass;
   final Class nativeFunctionClass;
   final Class opaqueClass;
-  final Class cArrayClass;
-  final Class cArraySizeClass;
+  final Class arrayClass;
+  final Class arraySizeClass;
+  final Field arraySizeDimension1Field;
+  final Field arraySizeDimension2Field;
+  final Field arraySizeDimension3Field;
+  final Field arraySizeDimension4Field;
+  final Field arraySizeDimension5Field;
+  final Field arraySizeDimensionsField;
   final Class pointerClass;
   final Class structClass;
   final Class ffiStructLayoutClass;
@@ -230,15 +237,23 @@
   final Procedure structPointerRef;
   final Procedure structPointerElemAt;
   final Procedure structArrayElemAt;
+  final Procedure arrayArrayElemAt;
+  final Procedure arrayArrayAssignAt;
   final Procedure asFunctionMethod;
   final Procedure asFunctionInternal;
   final Procedure sizeOfMethod;
   final Procedure lookupFunctionMethod;
   final Procedure fromFunctionMethod;
   final Field addressOfField;
-  final Field cArrayTypedDataBaseField;
+  final Field arrayTypedDataBaseField;
+  final Field arraySizeField;
+  final Field arrayNestedDimensionsField;
+  final Procedure arrayCheckIndex;
+  final Field arrayNestedDimensionsFlattened;
+  final Field arrayNestedDimensionsFirst;
+  final Field arrayNestedDimensionsRest;
   final Constructor structFromPointer;
-  final Constructor cArrayConstructor;
+  final Constructor arrayConstructor;
   final Procedure fromAddressInternal;
   final Procedure libraryLookupMethod;
   final Procedure abiMethod;
@@ -285,8 +300,20 @@
         allocatorClass = index.getClass('dart:ffi', 'Allocator'),
         nativeFunctionClass = index.getClass('dart:ffi', 'NativeFunction'),
         opaqueClass = index.getClass('dart:ffi', 'Opaque'),
-        cArrayClass = index.getClass('dart:ffi', 'Array'),
-        cArraySizeClass = index.getClass('dart:ffi', '_ArraySize'),
+        arrayClass = index.getClass('dart:ffi', 'Array'),
+        arraySizeClass = index.getClass('dart:ffi', '_ArraySize'),
+        arraySizeDimension1Field =
+            index.getMember('dart:ffi', '_ArraySize', 'dimension1'),
+        arraySizeDimension2Field =
+            index.getMember('dart:ffi', '_ArraySize', 'dimension2'),
+        arraySizeDimension3Field =
+            index.getMember('dart:ffi', '_ArraySize', 'dimension3'),
+        arraySizeDimension4Field =
+            index.getMember('dart:ffi', '_ArraySize', 'dimension4'),
+        arraySizeDimension5Field =
+            index.getMember('dart:ffi', '_ArraySize', 'dimension5'),
+        arraySizeDimensionsField =
+            index.getMember('dart:ffi', '_ArraySize', 'dimensions'),
         pointerClass = index.getClass('dart:ffi', 'Pointer'),
         structClass = index.getClass('dart:ffi', 'Struct'),
         ffiStructLayoutClass = index.getClass('dart:ffi', '_FfiStructLayout'),
@@ -305,11 +332,21 @@
         elementAtMethod = index.getMember('dart:ffi', 'Pointer', 'elementAt'),
         addressGetter = index.getMember('dart:ffi', 'Pointer', 'get:address'),
         addressOfField = index.getMember('dart:ffi', 'Struct', '_addressOf'),
-        cArrayTypedDataBaseField =
+        arrayTypedDataBaseField =
             index.getMember('dart:ffi', 'Array', '_typedDataBase'),
+        arraySizeField = index.getMember('dart:ffi', 'Array', '_size'),
+        arrayNestedDimensionsField =
+            index.getMember('dart:ffi', 'Array', '_nestedDimensions'),
+        arrayCheckIndex = index.getMember('dart:ffi', 'Array', '_checkIndex'),
+        arrayNestedDimensionsFlattened =
+            index.getMember('dart:ffi', 'Array', '_nestedDimensionsFlattened'),
+        arrayNestedDimensionsFirst =
+            index.getMember('dart:ffi', 'Array', '_nestedDimensionsFirst'),
+        arrayNestedDimensionsRest =
+            index.getMember('dart:ffi', 'Array', '_nestedDimensionsRest'),
         structFromPointer =
             index.getMember('dart:ffi', 'Struct', '_fromPointer'),
-        cArrayConstructor = index.getMember('dart:ffi', 'Array', '_'),
+        arrayConstructor = index.getMember('dart:ffi', 'Array', '_'),
         fromAddressInternal =
             index.getTopLevelMember('dart:ffi', '_fromAddress'),
         structPointerRef =
@@ -317,6 +354,8 @@
         structPointerElemAt =
             index.getMember('dart:ffi', 'StructPointer', '[]'),
         structArrayElemAt = index.getMember('dart:ffi', 'StructArray', '[]'),
+        arrayArrayElemAt = index.getMember('dart:ffi', 'ArrayArray', '[]'),
+        arrayArrayAssignAt = index.getMember('dart:ffi', 'ArrayArray', '[]='),
         asFunctionMethod =
             index.getMember('dart:ffi', 'NativeFunctionPointer', 'asFunction'),
         asFunctionInternal =
@@ -391,7 +430,8 @@
   DartType convertNativeTypeToDartType(DartType nativeType,
       {bool allowStructs = false,
       bool allowStructItself = false,
-      bool allowHandle = false}) {
+      bool allowHandle = false,
+      bool allowInlineArray = false}) {
     if (nativeType is! InterfaceType) {
       return null;
     }
@@ -399,6 +439,12 @@
     final Class nativeClass = native.classNode;
     final NativeType nativeType_ = getType(nativeClass);
 
+    if (nativeClass == arrayClass) {
+      if (!allowInlineArray) {
+        return null;
+      }
+      return nativeType;
+    }
     if (hierarchy.isSubclassOf(nativeClass, structClass)) {
       if (structClass == nativeClass) {
         return allowStructItself ? nativeType : null;
@@ -457,18 +503,22 @@
     return NativeType.values[index];
   }
 
+  ConstantExpression intListConstantExpression(List<int> values) =>
+      ConstantExpression(
+          ListConstant(InterfaceType(intClass, Nullability.legacy),
+              [for (var v in values) IntConstant(v)]),
+          InterfaceType(listClass, Nullability.legacy,
+              [InterfaceType(intClass, Nullability.legacy)]));
+
   /// Expression that queries VM internals at runtime to figure out on which ABI
   /// we are.
   Expression runtimeBranchOnLayout(Map<Abi, int> values) {
     return MethodInvocation(
-        ConstantExpression(
-            ListConstant(InterfaceType(intClass, Nullability.legacy), [
-              IntConstant(values[Abi.wordSize64]),
-              IntConstant(values[Abi.wordSize32Align32]),
-              IntConstant(values[Abi.wordSize32Align64])
-            ]),
-            InterfaceType(listClass, Nullability.legacy,
-                [InterfaceType(intClass, Nullability.legacy)])),
+        intListConstantExpression([
+          values[Abi.wordSize64],
+          values[Abi.wordSize32Align32],
+          values[Abi.wordSize32Align64]
+        ]),
         Name("[]"),
         Arguments([StaticInvocation(abiMethod, Arguments([]))]),
         listElementAt);
@@ -578,6 +628,102 @@
                 fileOffset),
             coreTypes.objectNonNullableRawType));
   }
+
+  bool isPrimitiveType(DartType type) {
+    if (type is InvalidType) {
+      return false;
+    }
+    if (type is NullType) {
+      return false;
+    }
+    if (!env.isSubtypeOf(
+        type,
+        InterfaceType(nativeTypesClasses[NativeType.kNativeType.index],
+            Nullability.legacy),
+        SubtypeCheckMode.ignoringNullabilities)) {
+      return false;
+    }
+    if (isPointerType(type)) {
+      return false;
+    }
+    if (type is InterfaceType) {
+      final nativeType = getType(type.classNode);
+      return nativeType != null;
+    }
+    return false;
+  }
+
+  bool isPointerType(DartType type) {
+    if (type is InvalidType) {
+      return false;
+    }
+    if (type is NullType) {
+      return false;
+    }
+    return env.isSubtypeOf(
+        type,
+        InterfaceType(pointerClass, Nullability.legacy, [
+          InterfaceType(nativeTypesClasses[NativeType.kNativeType.index],
+              Nullability.legacy)
+        ]),
+        SubtypeCheckMode.ignoringNullabilities);
+  }
+
+  bool isArrayType(DartType type) {
+    if (type is InvalidType) {
+      return false;
+    }
+    if (type is NullType) {
+      return false;
+    }
+    return env.isSubtypeOf(
+        type,
+        InterfaceType(arrayClass, Nullability.legacy, [
+          InterfaceType(nativeTypesClasses[NativeType.kNativeType.index],
+              Nullability.legacy)
+        ]),
+        SubtypeCheckMode.ignoringNullabilities);
+  }
+
+  /// Returns the single element type nested type argument of `Array`.
+  ///
+  /// `Array<Array<Array<Int8>>>` -> `Int8`.
+  DartType arraySingleElementType(DartType dartType) {
+    InterfaceType elementType = dartType as InterfaceType;
+    while (elementType.classNode == arrayClass) {
+      elementType = elementType.typeArguments[0] as InterfaceType;
+    }
+    return elementType;
+  }
+
+  /// Returns the number of dimensions of `Array`.
+  ///
+  /// `Array<Array<Array<Int8>>>` -> 3.
+  int arrayDimensions(DartType dartType) {
+    InterfaceType elementType = dartType as InterfaceType;
+    int dimensions = 0;
+    while (elementType.classNode == arrayClass) {
+      elementType = elementType.typeArguments[0] as InterfaceType;
+      dimensions++;
+    }
+    return dimensions;
+  }
+
+  bool isStructSubtype(DartType type) {
+    if (type is InvalidType) {
+      return false;
+    }
+    if (type is NullType) {
+      return false;
+    }
+    if (type is InterfaceType) {
+      if (type.classNode == structClass) {
+        return false;
+      }
+    }
+    return env.isSubtypeOf(type, InterfaceType(structClass, Nullability.legacy),
+        SubtypeCheckMode.ignoringNullabilities);
+  }
 }
 
 /// Contains all information collected by _FfiDefinitionTransformer that is
diff --git a/pkg/vm/lib/transformations/ffi_definitions.dart b/pkg/vm/lib/transformations/ffi_definitions.dart
index 1311f82..4569be7 100644
--- a/pkg/vm/lib/transformations/ffi_definitions.dart
+++ b/pkg/vm/lib/transformations/ffi_definitions.dart
@@ -13,10 +13,10 @@
         templateFfiFieldNull,
         templateFfiFieldCyclic,
         templateFfiFieldNoAnnotation,
-        templateFfiTypeInvalid,
         templateFfiTypeMismatch,
         templateFfiFieldInitializer,
         templateFfiSizeAnnotation,
+        templateFfiSizeAnnotationDimensions,
         templateFfiStructGeneric;
 
 import 'package:kernel/ast.dart' hide MapEntry;
@@ -213,48 +213,6 @@
     }
   }
 
-  bool _isPointerType(DartType type) {
-    if (type is InvalidType) {
-      return false;
-    }
-    return env.isSubtypeOf(
-        type,
-        InterfaceType(pointerClass, Nullability.legacy, [
-          InterfaceType(nativeTypesClasses[NativeType.kNativeType.index],
-              Nullability.legacy)
-        ]),
-        SubtypeCheckMode.ignoringNullabilities);
-  }
-
-  bool _isArrayType(DartType type) {
-    if (type is InvalidType) {
-      return false;
-    }
-    return env.isSubtypeOf(
-        type,
-        InterfaceType(cArrayClass, Nullability.legacy, [
-          InterfaceType(nativeTypesClasses[NativeType.kNativeType.index],
-              Nullability.legacy)
-        ]),
-        SubtypeCheckMode.ignoringNullabilities);
-  }
-
-  bool _isStructSubtype(DartType type) {
-    if (type is InvalidType) {
-      return false;
-    }
-    if (type is NullType) {
-      return false;
-    }
-    if (type is InterfaceType) {
-      if (type.classNode == structClass) {
-        return false;
-      }
-    }
-    return env.isSubtypeOf(type, InterfaceType(structClass, Nullability.legacy),
-        SubtypeCheckMode.ignoringNullabilities);
-  }
-
   /// Returns members of [node] that correspond to struct fields.
   ///
   /// Note that getters and setters that originate from an external field have
@@ -311,9 +269,9 @@
             f.fileUri);
         // This class is invalid, but continue reporting other errors on it.
         success = false;
-      } else if (_isPointerType(type) ||
-          _isStructSubtype(type) ||
-          _isArrayType(type)) {
+      } else if (isPointerType(type) ||
+          isStructSubtype(type) ||
+          isArrayType(type)) {
         if (nativeTypeAnnos.length != 0) {
           diagnosticReporter.report(
               templateFfiFieldNoAnnotation.withArguments(f.name.text),
@@ -323,24 +281,24 @@
           // This class is invalid, but continue reporting other errors on it.
           success = false;
         }
-        if (_isStructSubtype(type)) {
+        if (isStructSubtype(type)) {
           final clazz = (type as InterfaceType).classNode;
           structClassDependencies[node].add(clazz);
-        } else if (_isArrayType(type)) {
+        } else if (isArrayType(type)) {
           final sizeAnnotations = _getArraySizeAnnotations(f);
           if (sizeAnnotations.length == 1) {
-            final typeArgument = (type as InterfaceType).typeArguments.single;
-            if (_isStructSubtype(typeArgument)) {
-              final clazz = (typeArgument as InterfaceType).classNode;
+            final singleElementType = arraySingleElementType(type);
+            if (isStructSubtype(singleElementType)) {
+              final clazz = (singleElementType as InterfaceType).classNode;
               structClassDependencies[node].add(clazz);
-            } else if (_isArrayType(typeArgument)) {
+            }
+            if (arrayDimensions(type) != sizeAnnotations.single.length) {
               diagnosticReporter.report(
-                  templateFfiTypeInvalid.withArguments(
-                      typeArgument, currentLibrary.isNonNullableByDefault),
+                  templateFfiSizeAnnotationDimensions
+                      .withArguments(f.name.text),
                   f.fileOffset,
                   f.name.text.length,
                   f.fileUri);
-              success = false;
             }
           } else {
             diagnosticReporter.report(
@@ -446,38 +404,17 @@
       final dartType = _structFieldMemberType(m);
 
       NativeTypeCfe type;
-      if (_isPointerType(dartType)) {
-        type = PointerNativeTypeCfe();
-      } else if (_isStructSubtype(dartType)) {
-        final clazz = (dartType as InterfaceType).classNode;
-        type = structCache[clazz];
-        if (emptyStructs.contains(clazz)) {
-          diagnosticReporter.report(
-              templateFfiEmptyStruct.withArguments(clazz.name),
-              m.fileOffset,
-              1,
-              m.location.file);
-        }
-      } else if (_isArrayType(dartType)) {
+      if (isArrayType(dartType)) {
         final sizeAnnotations = _getArraySizeAnnotations(m).toList();
         if (sizeAnnotations.length == 1) {
-          final elementClass =
-              ((dartType as InterfaceType).typeArguments[0] as InterfaceType)
-                  .classNode;
-          final elementNativeType =
-              _getFieldType(elementClass) ?? NativeType.kStruct;
-          final arraySize = sizeAnnotations.single;
-          if (elementNativeType == NativeType.kStruct) {
-            type = ArrayNativeTypeCfe(structCache[elementClass], arraySize);
-          } else if (elementNativeType == NativeType.kPointer) {
-            type = ArrayNativeTypeCfe(PointerNativeTypeCfe(), arraySize);
-          } else {
-            type = ArrayNativeTypeCfe(
-                PrimitiveNativeTypeCfe(elementNativeType, elementClass),
-                arraySize);
-          }
+          final arrayDimensions = sizeAnnotations.single;
+          type = NativeTypeCfe(this, dartType,
+              structCache: structCache, arrayDimensions: arrayDimensions);
         }
+      } else if (isPointerType(dartType) || isStructSubtype(dartType)) {
+        type = NativeTypeCfe(this, dartType, structCache: structCache);
       } else {
+        // The C type is in the annotation, not the field type itself.
         final nativeTypeAnnos = _getNativeTypeAnnotations(m).toList();
         if (nativeTypeAnnos.length == 1) {
           final clazz = nativeTypeAnnos.first;
@@ -570,17 +507,29 @@
 
   List<Procedure> _generateMethodsForField(Field field, NativeTypeCfe type,
       Map<Abi, int> offsets, IndexedClass indexedClass) {
+    // TODO(johnniwinther): Avoid passing [indexedClass]. When compiling
+    // incrementally, [field] should already carry the references from
+    // [indexedClass].
     final getterStatement = type.generateGetterStatement(
         field.type, field.fileOffset, offsets, this);
+    Reference getterReference =
+        indexedClass?.lookupGetterReference(field.name) ??
+            field.getterReference;
+    assert(getterReference == field.getterReference,
+        "Unexpected getter reference for ${field}, found $getterReference.");
     final Procedure getter = Procedure(field.name, ProcedureKind.Getter,
         FunctionNode(getterStatement, returnType: field.type),
-        fileUri: field.fileUri,
-        reference: indexedClass?.lookupGetterReference(field.name))
+        fileUri: field.fileUri, reference: getterReference)
       ..fileOffset = field.fileOffset
       ..isNonNullableByDefault = field.isNonNullableByDefault;
 
     Procedure setter = null;
     if (!field.isFinal) {
+      Reference setterReference =
+          indexedClass?.lookupSetterReference(field.name) ??
+              field.setterReference;
+      assert(setterReference == field.setterReference,
+          "Unexpected setter reference for ${field}, found $setterReference.");
       final VariableDeclaration argument =
           VariableDeclaration('#v', type: field.type)
             ..fileOffset = field.fileOffset;
@@ -592,7 +541,7 @@
           FunctionNode(setterStatement,
               returnType: VoidType(), positionalParameters: [argument]),
           fileUri: field.fileUri,
-          reference: indexedClass?.lookupSetterReference(field.name))
+          reference: setterReference)
         ..fileOffset = field.fileOffset
         ..isNonNullableByDefault = field.isNonNullableByDefault;
     }
@@ -648,13 +597,41 @@
         .where((klass) => _getFieldType(klass) != null);
   }
 
-  Iterable<int> _getArraySizeAnnotations(Member node) {
+  Iterable<List<int>> _getArraySizeAnnotations(Member node) {
     return node.annotations
         .whereType<ConstantExpression>()
         .map((e) => e.constant)
         .whereType<InstanceConstant>()
-        .where((e) => e.classNode == cArraySizeClass)
-        .map((e) => (e.fieldValues.values.single as IntConstant).value);
+        .where((e) => e.classNode == arraySizeClass)
+        .map(_arraySize);
+  }
+
+  List<int> _arraySize(InstanceConstant constant) {
+    final dimensions =
+        constant.fieldValues[arraySizeDimensionsField.getterReference];
+    if (dimensions != null) {
+      if (dimensions is ListConstant) {
+        final result = dimensions.entries
+            .whereType<IntConstant>()
+            .map((e) => e.value)
+            .toList();
+        assert(result.length > 0);
+        return result;
+      }
+    }
+    final dimensionFields = [
+      arraySizeDimension1Field,
+      arraySizeDimension2Field,
+      arraySizeDimension3Field,
+      arraySizeDimension4Field,
+      arraySizeDimension5Field
+    ];
+    final result = dimensionFields
+        .map((f) => constant.fieldValues[f.getterReference])
+        .whereType<IntConstant>()
+        .map((c) => c.value)
+        .toList();
+    return result;
   }
 }
 
@@ -677,6 +654,37 @@
 /// This algebraic data structure does not stand on its own but refers
 /// intimately to AST nodes such as [Class].
 abstract class NativeTypeCfe {
+  factory NativeTypeCfe(FfiTransformer transformer, DartType dartType,
+      {List<int> arrayDimensions,
+      Map<Class, StructNativeTypeCfe> structCache = const {}}) {
+    if (transformer.isPrimitiveType(dartType)) {
+      final clazz = (dartType as InterfaceType).classNode;
+      final nativeType = transformer.getType(clazz);
+      return PrimitiveNativeTypeCfe(nativeType, clazz);
+    }
+    if (transformer.isPointerType(dartType)) {
+      return PointerNativeTypeCfe();
+    }
+    if (transformer.isStructSubtype(dartType)) {
+      final clazz = (dartType as InterfaceType).classNode;
+      if (structCache.containsKey(clazz)) {
+        return structCache[clazz];
+      } else {
+        throw "$clazz not found in structCache";
+      }
+    }
+    if (transformer.isArrayType(dartType)) {
+      if (arrayDimensions == null) {
+        throw "Must have array dimensions for ArrayType";
+      }
+      final elementType = transformer.arraySingleElementType(dartType);
+      final elementCfeType =
+          NativeTypeCfe(transformer, elementType, structCache: structCache);
+      return ArrayNativeTypeCfe.multi(elementCfeType, arrayDimensions);
+    }
+    throw "Invalid type $dartType";
+  }
+
   /// The size in bytes per [Abi].
   Map<Abi, int> get size;
 
@@ -947,6 +955,37 @@
 
   ArrayNativeTypeCfe(this.elementType, this.length);
 
+  factory ArrayNativeTypeCfe.multi(
+      NativeTypeCfe elementType, List<int> dimensions) {
+    if (dimensions.length == 1) {
+      return ArrayNativeTypeCfe(elementType, dimensions.single);
+    }
+    return ArrayNativeTypeCfe(
+        ArrayNativeTypeCfe.multi(elementType, dimensions.sublist(1)),
+        dimensions.first);
+  }
+
+  List<int> get dimensions {
+    final elementType = this.elementType;
+    if (elementType is ArrayNativeTypeCfe) {
+      return [length, ...elementType.dimensions];
+    }
+    return [length];
+  }
+
+  List<int> get nestedDimensions => dimensions.sublist(1);
+
+  int get dimensionsFlattened =>
+      dimensions.fold(1, (accumulator, element) => accumulator * element);
+
+  NativeTypeCfe get singleElementType {
+    final elementType = this.elementType;
+    if (elementType is ArrayNativeTypeCfe) {
+      return elementType.singleElementType;
+    }
+    return elementType;
+  }
+
   @override
   Map<Abi, int> get size =>
       elementType.size.map((abi, size) => MapEntry(abi, size * length));
@@ -954,13 +993,14 @@
   @override
   Map<Abi, int> get alignment => elementType.alignment;
 
+  // Note that we flatten multi dimensional arrays.
   @override
   Constant generateConstant(FfiTransformer transformer) =>
       InstanceConstant(transformer.ffiInlineArrayClass.reference, [], {
         transformer.ffiInlineArrayElementTypeField.getterReference:
-            elementType.generateConstant(transformer),
+            singleElementType.generateConstant(transformer),
         transformer.ffiInlineArrayLengthField.getterReference:
-            IntConstant(length)
+            IntConstant(dimensionsFlattened)
       });
 
   /// Sample output for `Array<Int8> get x =>`:
@@ -976,7 +1016,7 @@
     InterfaceType typeArgument =
         (dartType as InterfaceType).typeArguments.single as InterfaceType;
     return ReturnStatement(ConstructorInvocation(
-        transformer.cArrayConstructor,
+        transformer.arrayConstructor,
         Arguments([
           transformer.typedDataBaseOffset(
               PropertyGet(ThisExpression(), transformer.addressOfField.name,
@@ -986,7 +1026,8 @@
               transformer.runtimeBranchOnLayout(size),
               typeArgument,
               fileOffset),
-          ConstantExpression(IntConstant(length))
+          ConstantExpression(IntConstant(length)),
+          transformer.intListConstantExpression(nestedDimensions)
         ], types: [
           dartType
         ]))
@@ -1014,8 +1055,8 @@
             transformer.runtimeBranchOnLayout(offsets),
             PropertyGet(
                 VariableGet(argument),
-                transformer.cArrayTypedDataBaseField.name,
-                transformer.cArrayTypedDataBaseField)
+                transformer.arrayTypedDataBaseField.name,
+                transformer.arrayTypedDataBaseField)
               ..fileOffset = fileOffset,
             ConstantExpression(IntConstant(0)),
             transformer.runtimeBranchOnLayout(size),
diff --git a/pkg/vm/lib/transformations/ffi_use_sites.dart b/pkg/vm/lib/transformations/ffi_use_sites.dart
index 377721f..aad89d8 100644
--- a/pkg/vm/lib/transformations/ffi_use_sites.dart
+++ b/pkg/vm/lib/transformations/ffi_use_sites.dart
@@ -188,6 +188,20 @@
         _ensureNativeTypeValid(nativeType, node, allowStructItself: false);
 
         return _replaceRefArray(node);
+      } else if (target == arrayArrayElemAt) {
+        final DartType nativeType = node.arguments.types[0];
+
+        _ensureNativeTypeValid(nativeType, node,
+            allowStructItself: false, allowInlineArray: true);
+
+        return _replaceArrayArrayElemAt(node);
+      } else if (target == arrayArrayAssignAt) {
+        final DartType nativeType = node.arguments.types[0];
+
+        _ensureNativeTypeValid(nativeType, node,
+            allowStructItself: false, allowInlineArray: true);
+
+        return _replaceArrayArrayElemAt(node, setter: true);
       } else if (target == sizeOfMethod) {
         final DartType nativeType = node.arguments.types[0];
 
@@ -469,7 +483,7 @@
 
     final typedDataBasePrime = typedDataBaseOffset(
         PropertyGet(NullCheck(node.arguments.positional[0]),
-            cArrayTypedDataBaseField.name, cArrayTypedDataBaseField),
+            arrayTypedDataBaseField.name, arrayTypedDataBaseField),
         MethodInvocation(node.arguments.positional[1], numMultiplication.name,
             Arguments([StaticGet(clazz.fields.single)]), numMultiplication),
         StaticGet(clazz.fields.single),
@@ -479,6 +493,146 @@
     return ConstructorInvocation(constructor, Arguments([typedDataBasePrime]));
   }
 
+  /// Generates an expression that returns a new `Array<dartType>`.
+  ///
+  /// Sample input getter:
+  /// ```
+  /// this<Array<T>>[index]
+  /// ```
+  ///
+  /// Sample output getter:
+  ///
+  /// ```
+  /// Array #array = this!;
+  /// int #index = index!;
+  /// #array._checkIndex(#index);
+  /// int #singleElementSize = _inlineSizeOf<innermost(T)>();
+  /// int #elementSize = #array.nestedDimensionsFlattened * #singleElementSize;
+  /// int #offset = #elementSize * #index;
+  ///
+  /// new Array<T>._(
+  ///   typedDataBaseOffset(#array._typedDataBase, #offset, #elementSize),
+  ///   #array.nestedDimensionsFirst,
+  ///   #array.nestedDimensionsRest
+  /// )
+  /// ```
+  ///
+  /// Sample input setter:
+  /// ```
+  /// this<Array<T>>[index] = value
+  /// ```
+  ///
+  /// Sample output setter:
+  ///
+  /// ```
+  /// Array #array = this!;
+  /// int #index = index!;
+  /// #array._checkIndex(#index);
+  /// int #singleElementSize = _inlineSizeOf<innermost(T)>();
+  /// int #elementSize = #array.nestedDimensionsFlattened * #singleElementSize;
+  /// int #offset = #elementSize * #index;
+  ///
+  /// _memCopy(
+  ///   #array._typedDataBase, #offset, value._typedDataBase, 0, #elementSize)
+  /// ```
+  Expression _replaceArrayArrayElemAt(StaticInvocation node,
+      {bool setter: false}) {
+    final dartType = node.arguments.types[0];
+    final elementType = arraySingleElementType(dartType as InterfaceType);
+
+    final arrayVar = VariableDeclaration("#array",
+        initializer: NullCheck(node.arguments.positional[0]),
+        type: InterfaceType(arrayClass, Nullability.nonNullable))
+      ..fileOffset = node.fileOffset;
+    final indexVar = VariableDeclaration("#index",
+        initializer: NullCheck(node.arguments.positional[1]),
+        type: coreTypes.intNonNullableRawType)
+      ..fileOffset = node.fileOffset;
+    final singleElementSizeVar = VariableDeclaration("#singleElementSize",
+        initializer: _inlineSizeOf(elementType),
+        type: coreTypes.intNonNullableRawType)
+      ..fileOffset = node.fileOffset;
+    final elementSizeVar = VariableDeclaration("#elementSize",
+        initializer: MethodInvocation(
+            VariableGet(singleElementSizeVar),
+            numMultiplication.name,
+            Arguments([
+              PropertyGet(
+                  VariableGet(arrayVar),
+                  arrayNestedDimensionsFlattened.name,
+                  arrayNestedDimensionsFlattened)
+            ]),
+            numMultiplication),
+        type: coreTypes.intNonNullableRawType)
+      ..fileOffset = node.fileOffset;
+    final offsetVar = VariableDeclaration("#offset",
+        initializer: MethodInvocation(
+            VariableGet(elementSizeVar),
+            numMultiplication.name,
+            Arguments([
+              VariableGet(indexVar),
+            ]),
+            numMultiplication),
+        type: coreTypes.intNonNullableRawType)
+      ..fileOffset = node.fileOffset;
+
+    final checkIndexAndLocalVars = Block([
+      arrayVar,
+      indexVar,
+      ExpressionStatement(MethodInvocation(
+          VariableGet(arrayVar),
+          arrayCheckIndex.name,
+          Arguments([VariableGet(indexVar)]),
+          arrayCheckIndex)),
+      singleElementSizeVar,
+      elementSizeVar,
+      offsetVar
+    ]);
+
+    if (!setter) {
+      // `[]`
+      return BlockExpression(
+          checkIndexAndLocalVars,
+          ConstructorInvocation(
+              arrayConstructor,
+              Arguments([
+                typedDataBaseOffset(
+                    PropertyGet(VariableGet(arrayVar),
+                        arrayTypedDataBaseField.name, arrayTypedDataBaseField),
+                    VariableGet(offsetVar),
+                    VariableGet(elementSizeVar),
+                    dartType,
+                    node.fileOffset),
+                PropertyGet(
+                    VariableGet(arrayVar),
+                    arrayNestedDimensionsFirst.name,
+                    arrayNestedDimensionsFirst),
+                PropertyGet(VariableGet(arrayVar),
+                    arrayNestedDimensionsRest.name, arrayNestedDimensionsRest)
+              ], types: [
+                dartType
+              ])));
+    }
+
+    // `[]=`
+    return BlockExpression(
+        checkIndexAndLocalVars,
+        StaticInvocation(
+            memCopy,
+            Arguments([
+              PropertyGet(VariableGet(arrayVar), arrayTypedDataBaseField.name,
+                  arrayTypedDataBaseField)
+                ..fileOffset = node.fileOffset,
+              VariableGet(offsetVar),
+              PropertyGet(node.arguments.positional[2],
+                  arrayTypedDataBaseField.name, arrayTypedDataBaseField)
+                ..fileOffset = node.fileOffset,
+              ConstantExpression(IntConstant(0)),
+              VariableGet(elementSizeVar),
+            ]))
+          ..fileOffset = node.fileOffset);
+  }
+
   @override
   visitMethodInvocation(MethodInvocation node) {
     super.visitMethodInvocation(node);
@@ -543,11 +697,14 @@
   }
 
   void _ensureNativeTypeValid(DartType nativeType, Expression node,
-      {bool allowHandle: false, bool allowStructItself = true}) {
+      {bool allowHandle: false,
+      bool allowStructItself = true,
+      bool allowInlineArray = false}) {
     if (!_nativeTypeValid(nativeType,
         allowStructs: true,
         allowStructItself: allowStructItself,
-        allowHandle: allowHandle)) {
+        allowHandle: allowHandle,
+        allowInlineArray: allowInlineArray)) {
       diagnosticReporter.report(
           templateFfiTypeInvalid.withArguments(
               nativeType, currentLibrary.isNonNullableByDefault),
@@ -586,11 +743,13 @@
   bool _nativeTypeValid(DartType nativeType,
       {bool allowStructs: false,
       bool allowStructItself = false,
-      bool allowHandle = false}) {
+      bool allowHandle = false,
+      bool allowInlineArray = false}) {
     return convertNativeTypeToDartType(nativeType,
             allowStructs: allowStructs,
             allowStructItself: allowStructItself,
-            allowHandle: allowHandle) !=
+            allowHandle: allowHandle,
+            allowInlineArray: allowInlineArray) !=
         null;
   }
 
@@ -621,7 +780,7 @@
           : null;
     }
 
-    if (!nativeTypesClasses.contains(klass) && klass != cArrayClass) {
+    if (!nativeTypesClasses.contains(klass) && klass != arrayClass) {
       for (final parent in nativeTypesClasses) {
         if (hierarchy.isSubtypeOf(klass, parent)) {
           return parent;
diff --git a/pkg/vm/lib/transformations/type_flow/transformer.dart b/pkg/vm/lib/transformations/type_flow/transformer.dart
index ac88a41..ff602a2 100644
--- a/pkg/vm/lib/transformations/type_flow/transformer.dart
+++ b/pkg/vm/lib/transformations/type_flow/transformer.dart
@@ -554,7 +554,17 @@
                   isFieldInitializerReachable(f) &&
                   mayHaveSideEffects(f.initializer)) ||
               (f.isLate && f.isFinal)) ||
-      isMemberReferencedFromNativeCode(f);
+      isMemberReferencedFromNativeCode(f) ||
+      _isInstanceFieldOfAllocatedEnum(f);
+
+  /// Preserve instance fields of allocated enums as VM relies on their
+  /// existence. Non-allocated enums are converted into ordinary classes during
+  /// the 2nd pass.
+  bool _isInstanceFieldOfAllocatedEnum(Field node) =>
+      !node.isStatic &&
+      node.enclosingClass != null &&
+      node.enclosingClass.isEnum &&
+      isClassAllocated(node.enclosingClass);
 
   void addClassUsedInType(Class c) {
     if (_classesUsedInType.add(c)) {
@@ -687,14 +697,17 @@
               positionalParameters: [parameter], returnType: const VoidType())
             ..fileOffset = field.fileOffset,
           isAbstract: isAbstract,
-          fileUri: field.fileUri);
+          fileUri: field.fileUri,
+          reference: field.setterReference);
       if (!isAbstract) {
         _extraMembersWithReachableBody.add(accessor);
       }
     } else {
       accessor = new Procedure(field.name, ProcedureKind.Getter,
           new FunctionNode(null, returnType: field.type),
-          isAbstract: true, fileUri: field.fileUri);
+          isAbstract: true,
+          fileUri: field.fileUri,
+          reference: field.getterReference);
     }
     accessor.fileOffset = field.fileOffset;
     field.enclosingClass.addProcedure(accessor);
@@ -739,6 +752,12 @@
     }
     return _removedFields[target] ?? target;
   }
+
+  bool isGetterReferenceReused(Field node) =>
+      _gettersForRemovedFields.containsKey(node);
+
+  bool isSetterReferenceReused(Field node) =>
+      _settersForRemovedFields.containsKey(node);
 }
 
 /// Visits Dart types and collects all classes and typedefs used in types.
@@ -1246,7 +1265,7 @@
     for (Source source in component.uriToSource.values) {
       source?.constantCoverageConstructors?.removeWhere((Reference reference) {
         Member node = reference.asMember;
-        return !shaker.isMemberUsed(node) && !_preserveSpecialMember(node);
+        return !shaker.isMemberUsed(node);
       });
     }
   }
@@ -1282,6 +1301,10 @@
       debugPrint('Dropped class ${node.name}');
       // Ensure that kernel file writer will not be able to
       // write a dangling reference to the deleted class.
+      assert(
+          node.reference.node == node,
+          "Trying to remove canonical name from reference on $node which has "
+          "been repurposed for ${node.reference.node}.");
       node.reference.canonicalName = null;
       Statistics.classesDropped++;
       return removalSentinel; // Remove the class.
@@ -1297,6 +1320,7 @@
       node.implementedTypes.clear();
       node.typeParameters.clear();
       node.isAbstract = true;
+      node.isEnum = false;
       // Mixin applications cannot have static members.
       assert(node.mixedInType == null);
       node.annotations = const <Expression>[];
@@ -1305,6 +1329,7 @@
     if (!shaker.isClassAllocated(node)) {
       debugPrint('Class ${node.name} converted to abstract');
       node.isAbstract = true;
+      node.isEnum = false;
     }
 
     node.transformOrRemoveChildren(this);
@@ -1312,19 +1337,41 @@
     return node;
   }
 
-  /// Preserve instance fields of enums as VM relies on their existence.
-  bool _preserveSpecialMember(Member node) =>
-      node is Field &&
-      !node.isStatic &&
-      node.enclosingClass != null &&
-      node.enclosingClass.isEnum;
-
   @override
   Member defaultMember(Member node, TreeNode removalSentinel) {
-    if (!shaker.isMemberUsed(node) && !_preserveSpecialMember(node)) {
+    if (!shaker.isMemberUsed(node)) {
       // Ensure that kernel file writer will not be able to
       // write a dangling reference to the deleted member.
-      node.reference.canonicalName = null;
+      if (node is Field) {
+        if (!shaker.fieldMorpher.isGetterReferenceReused(node)) {
+          // The getter reference hasn't be repurposed for another node so we
+          // reset the canonical name to ensure that the getter reference
+          // cannot be serialized.
+          assert(
+              node.getterReference.node == node,
+              "Trying to remove canonical name from getter reference on $node "
+              "which has been repurposed for ${node.getterReference.node}.");
+          node.getterReference.canonicalName = null;
+        }
+        if (node.hasSetter) {
+          if (!shaker.fieldMorpher.isSetterReferenceReused(node)) {
+            // The setter reference hasn't be repurposed for another node so we
+            // reset the canonical name to ensure that the setter reference
+            // cannot be serialized.
+            assert(
+                node.setterReference.node == node,
+                "Trying to remove canonical name from reference on $node which "
+                "has been repurposed for ${node.setterReference.node}.");
+            node.setterReference.canonicalName = null;
+          }
+        }
+      } else {
+        assert(
+            node.reference.node == node,
+            "Trying to remove canonical name from reference on $node which has "
+            "been repurposed for ${node.reference.node}.");
+        node.reference.canonicalName = null;
+      }
       Statistics.membersDropped++;
       return removalSentinel;
     }
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/enum_used_as_type.dart b/pkg/vm/testcases/transformations/type_flow/transformer/enum_used_as_type.dart
new file mode 100644
index 0000000..da8fb17
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/enum_used_as_type.dart
@@ -0,0 +1,16 @@
+// 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.
+
+enum Enum { a }
+
+class Class {
+  int method(Enum e) => e.index;
+}
+
+main() {
+  List list = [];
+  if (list.isNotEmpty) {
+    new Class().method(null as dynamic);
+  }
+}
diff --git a/pkg/vm/testcases/transformations/type_flow/transformer/enum_used_as_type.dart.expect b/pkg/vm/testcases/transformations/type_flow/transformer/enum_used_as_type.dart.expect
new file mode 100644
index 0000000..89d9a31
--- /dev/null
+++ b/pkg/vm/testcases/transformations/type_flow/transformer/enum_used_as_type.dart.expect
@@ -0,0 +1,21 @@
+library #lib;
+import self as self;
+import "dart:core" as core;
+import "dart:_internal" as _in;
+
+abstract class Enum extends core::Object {
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1]  abstract get index() → core::int*;
+}
+class Class extends core::Object {
+  synthetic constructor •() → self::Class*
+    : super core::Object::•()
+    ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:2,getterSelectorId:3]  method method() → core::int*
+    return [@vm.inferred-type.metadata=!](#C1).{self::Enum::index};
+}
+static method main() → dynamic {
+  core::List<dynamic>* list = [@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::•<dynamic>(0);
+  if([@vm.direct-call.metadata=dart.core::_GrowableList.isNotEmpty] [@vm.inferred-type.metadata=dart.core::bool] list.{core::Iterable::isNotEmpty}) {
+    let final self::Class* #t1 = new self::Class::•() in let final self::Enum* #t2 = _in::unsafeCast<self::Enum*>(_in::unsafeCast<dynamic>(null)) in [@vm.direct-call.metadata=#lib::Class.method] [@vm.inferred-type.metadata=!? (skip check)] #t1.{self::Class::method}();
+  }
+}
diff --git a/runtime/bin/ffi_test/ffi_test_functions_generated.cc b/runtime/bin/ffi_test/ffi_test_functions_generated.cc
index 220b5c1..a352c54 100644
--- a/runtime/bin/ffi_test/ffi_test_functions_generated.cc
+++ b/runtime/bin/ffi_test/ffi_test_functions_generated.cc
@@ -451,6 +451,22 @@
   int16_t a2[2];
 };
 
+struct Struct8BytesInlineArrayMultiDimensionalInt {
+  uint8_t a0[2][2][2];
+};
+
+struct Struct32BytesInlineArrayMultiDimensionalInt {
+  uint8_t a0[2][2][2][2][2];
+};
+
+struct Struct64BytesInlineArrayMultiDimensionalInt {
+  uint8_t a0[2][2][2][2][2][2];
+};
+
+struct Struct4BytesInlineArrayMultiDimensionalInt {
+  Struct1ByteInt a0[2][2];
+};
+
 // Used for testing structs by value.
 // Smallest struct with data.
 // 10 struct arguments will exhaust available registers.
@@ -3826,6 +3842,160 @@
 }
 
 // Used for testing structs by value.
+// Test multi dimensional inline array struct as argument.
+DART_EXPORT uint32_t PassUint8Struct32BytesInlineArrayMultiDimensionalI(
+    uint8_t a0,
+    Struct32BytesInlineArrayMultiDimensionalInt a1,
+    uint8_t a2,
+    Struct8BytesInlineArrayMultiDimensionalInt a3,
+    uint8_t a4,
+    Struct8BytesInlineArrayMultiDimensionalInt a5,
+    uint8_t a6) {
+  std::cout << "PassUint8Struct32BytesInlineArrayMultiDimensionalI"
+            << "(" << static_cast<int>(a0) << ", ([[[[["
+            << static_cast<int>(a1.a0[0][0][0][0][0]) << ", "
+            << static_cast<int>(a1.a0[0][0][0][0][1]) << "], ["
+            << static_cast<int>(a1.a0[0][0][0][1][0]) << ", "
+            << static_cast<int>(a1.a0[0][0][0][1][1]) << "]], [["
+            << static_cast<int>(a1.a0[0][0][1][0][0]) << ", "
+            << static_cast<int>(a1.a0[0][0][1][0][1]) << "], ["
+            << static_cast<int>(a1.a0[0][0][1][1][0]) << ", "
+            << static_cast<int>(a1.a0[0][0][1][1][1]) << "]]], [[["
+            << static_cast<int>(a1.a0[0][1][0][0][0]) << ", "
+            << static_cast<int>(a1.a0[0][1][0][0][1]) << "], ["
+            << static_cast<int>(a1.a0[0][1][0][1][0]) << ", "
+            << static_cast<int>(a1.a0[0][1][0][1][1]) << "]], [["
+            << static_cast<int>(a1.a0[0][1][1][0][0]) << ", "
+            << static_cast<int>(a1.a0[0][1][1][0][1]) << "], ["
+            << static_cast<int>(a1.a0[0][1][1][1][0]) << ", "
+            << static_cast<int>(a1.a0[0][1][1][1][1]) << "]]]], [[[["
+            << static_cast<int>(a1.a0[1][0][0][0][0]) << ", "
+            << static_cast<int>(a1.a0[1][0][0][0][1]) << "], ["
+            << static_cast<int>(a1.a0[1][0][0][1][0]) << ", "
+            << static_cast<int>(a1.a0[1][0][0][1][1]) << "]], [["
+            << static_cast<int>(a1.a0[1][0][1][0][0]) << ", "
+            << static_cast<int>(a1.a0[1][0][1][0][1]) << "], ["
+            << static_cast<int>(a1.a0[1][0][1][1][0]) << ", "
+            << static_cast<int>(a1.a0[1][0][1][1][1]) << "]]], [[["
+            << static_cast<int>(a1.a0[1][1][0][0][0]) << ", "
+            << static_cast<int>(a1.a0[1][1][0][0][1]) << "], ["
+            << static_cast<int>(a1.a0[1][1][0][1][0]) << ", "
+            << static_cast<int>(a1.a0[1][1][0][1][1]) << "]], [["
+            << static_cast<int>(a1.a0[1][1][1][0][0]) << ", "
+            << static_cast<int>(a1.a0[1][1][1][0][1]) << "], ["
+            << static_cast<int>(a1.a0[1][1][1][1][0]) << ", "
+            << static_cast<int>(a1.a0[1][1][1][1][1]) << "]]]]]), "
+            << static_cast<int>(a2) << ", ([[["
+            << static_cast<int>(a3.a0[0][0][0]) << ", "
+            << static_cast<int>(a3.a0[0][0][1]) << "], ["
+            << static_cast<int>(a3.a0[0][1][0]) << ", "
+            << static_cast<int>(a3.a0[0][1][1]) << "]], [["
+            << static_cast<int>(a3.a0[1][0][0]) << ", "
+            << static_cast<int>(a3.a0[1][0][1]) << "], ["
+            << static_cast<int>(a3.a0[1][1][0]) << ", "
+            << static_cast<int>(a3.a0[1][1][1]) << "]]]), "
+            << static_cast<int>(a4) << ", ([[["
+            << static_cast<int>(a5.a0[0][0][0]) << ", "
+            << static_cast<int>(a5.a0[0][0][1]) << "], ["
+            << static_cast<int>(a5.a0[0][1][0]) << ", "
+            << static_cast<int>(a5.a0[0][1][1]) << "]], [["
+            << static_cast<int>(a5.a0[1][0][0]) << ", "
+            << static_cast<int>(a5.a0[1][0][1]) << "], ["
+            << static_cast<int>(a5.a0[1][1][0]) << ", "
+            << static_cast<int>(a5.a0[1][1][1]) << "]]]), "
+            << static_cast<int>(a6) << ")"
+            << "\n";
+
+  uint32_t result = 0;
+
+  result += a0;
+  result += a1.a0[0][0][0][0][0];
+  result += a1.a0[0][0][0][0][1];
+  result += a1.a0[0][0][0][1][0];
+  result += a1.a0[0][0][0][1][1];
+  result += a1.a0[0][0][1][0][0];
+  result += a1.a0[0][0][1][0][1];
+  result += a1.a0[0][0][1][1][0];
+  result += a1.a0[0][0][1][1][1];
+  result += a1.a0[0][1][0][0][0];
+  result += a1.a0[0][1][0][0][1];
+  result += a1.a0[0][1][0][1][0];
+  result += a1.a0[0][1][0][1][1];
+  result += a1.a0[0][1][1][0][0];
+  result += a1.a0[0][1][1][0][1];
+  result += a1.a0[0][1][1][1][0];
+  result += a1.a0[0][1][1][1][1];
+  result += a1.a0[1][0][0][0][0];
+  result += a1.a0[1][0][0][0][1];
+  result += a1.a0[1][0][0][1][0];
+  result += a1.a0[1][0][0][1][1];
+  result += a1.a0[1][0][1][0][0];
+  result += a1.a0[1][0][1][0][1];
+  result += a1.a0[1][0][1][1][0];
+  result += a1.a0[1][0][1][1][1];
+  result += a1.a0[1][1][0][0][0];
+  result += a1.a0[1][1][0][0][1];
+  result += a1.a0[1][1][0][1][0];
+  result += a1.a0[1][1][0][1][1];
+  result += a1.a0[1][1][1][0][0];
+  result += a1.a0[1][1][1][0][1];
+  result += a1.a0[1][1][1][1][0];
+  result += a1.a0[1][1][1][1][1];
+  result += a2;
+  result += a3.a0[0][0][0];
+  result += a3.a0[0][0][1];
+  result += a3.a0[0][1][0];
+  result += a3.a0[0][1][1];
+  result += a3.a0[1][0][0];
+  result += a3.a0[1][0][1];
+  result += a3.a0[1][1][0];
+  result += a3.a0[1][1][1];
+  result += a4;
+  result += a5.a0[0][0][0];
+  result += a5.a0[0][0][1];
+  result += a5.a0[0][1][0];
+  result += a5.a0[0][1][1];
+  result += a5.a0[1][0][0];
+  result += a5.a0[1][0][1];
+  result += a5.a0[1][1][0];
+  result += a5.a0[1][1][1];
+  result += a6;
+
+  std::cout << "result = " << result << "\n";
+
+  return result;
+}
+
+// Used for testing structs by value.
+// Test struct in multi dimensional inline array.
+DART_EXPORT uint32_t PassUint8Struct4BytesInlineArrayMultiDimensionalIn(
+    uint8_t a0,
+    Struct4BytesInlineArrayMultiDimensionalInt a1,
+    uint8_t a2) {
+  std::cout << "PassUint8Struct4BytesInlineArrayMultiDimensionalIn"
+            << "(" << static_cast<int>(a0) << ", ([[("
+            << static_cast<int>(a1.a0[0][0].a0) << "), ("
+            << static_cast<int>(a1.a0[0][1].a0) << ")], [("
+            << static_cast<int>(a1.a0[1][0].a0) << "), ("
+            << static_cast<int>(a1.a0[1][1].a0) << ")]]), "
+            << static_cast<int>(a2) << ")"
+            << "\n";
+
+  uint32_t result = 0;
+
+  result += a0;
+  result += a1.a0[0][0].a0;
+  result += a1.a0[0][1].a0;
+  result += a1.a0[1][0].a0;
+  result += a1.a0[1][1].a0;
+  result += a2;
+
+  std::cout << "result = " << result << "\n";
+
+  return result;
+}
+
+// Used for testing structs by value.
 // Smallest struct with data.
 DART_EXPORT Struct1ByteInt ReturnStruct1ByteInt(int8_t a0) {
   std::cout << "ReturnStruct1ByteInt"
@@ -9997,6 +10167,206 @@
 }
 
 // Used for testing structs by value.
+// Test multi dimensional inline array struct as argument.
+DART_EXPORT intptr_t TestPassUint8Struct32BytesInlineArrayMultiDimensionalI(
+    // NOLINTNEXTLINE(whitespace/parens)
+    uint32_t (*f)(uint8_t a0,
+                  Struct32BytesInlineArrayMultiDimensionalInt a1,
+                  uint8_t a2,
+                  Struct8BytesInlineArrayMultiDimensionalInt a3,
+                  uint8_t a4,
+                  Struct8BytesInlineArrayMultiDimensionalInt a5,
+                  uint8_t a6)) {
+  uint8_t a0;
+  Struct32BytesInlineArrayMultiDimensionalInt a1;
+  uint8_t a2;
+  Struct8BytesInlineArrayMultiDimensionalInt a3;
+  uint8_t a4;
+  Struct8BytesInlineArrayMultiDimensionalInt a5;
+  uint8_t a6;
+
+  a0 = 1;
+  a1.a0[0][0][0][0][0] = 2;
+  a1.a0[0][0][0][0][1] = 3;
+  a1.a0[0][0][0][1][0] = 4;
+  a1.a0[0][0][0][1][1] = 5;
+  a1.a0[0][0][1][0][0] = 6;
+  a1.a0[0][0][1][0][1] = 7;
+  a1.a0[0][0][1][1][0] = 8;
+  a1.a0[0][0][1][1][1] = 9;
+  a1.a0[0][1][0][0][0] = 10;
+  a1.a0[0][1][0][0][1] = 11;
+  a1.a0[0][1][0][1][0] = 12;
+  a1.a0[0][1][0][1][1] = 13;
+  a1.a0[0][1][1][0][0] = 14;
+  a1.a0[0][1][1][0][1] = 15;
+  a1.a0[0][1][1][1][0] = 16;
+  a1.a0[0][1][1][1][1] = 17;
+  a1.a0[1][0][0][0][0] = 18;
+  a1.a0[1][0][0][0][1] = 19;
+  a1.a0[1][0][0][1][0] = 20;
+  a1.a0[1][0][0][1][1] = 21;
+  a1.a0[1][0][1][0][0] = 22;
+  a1.a0[1][0][1][0][1] = 23;
+  a1.a0[1][0][1][1][0] = 24;
+  a1.a0[1][0][1][1][1] = 25;
+  a1.a0[1][1][0][0][0] = 26;
+  a1.a0[1][1][0][0][1] = 27;
+  a1.a0[1][1][0][1][0] = 28;
+  a1.a0[1][1][0][1][1] = 29;
+  a1.a0[1][1][1][0][0] = 30;
+  a1.a0[1][1][1][0][1] = 31;
+  a1.a0[1][1][1][1][0] = 32;
+  a1.a0[1][1][1][1][1] = 33;
+  a2 = 34;
+  a3.a0[0][0][0] = 35;
+  a3.a0[0][0][1] = 36;
+  a3.a0[0][1][0] = 37;
+  a3.a0[0][1][1] = 38;
+  a3.a0[1][0][0] = 39;
+  a3.a0[1][0][1] = 40;
+  a3.a0[1][1][0] = 41;
+  a3.a0[1][1][1] = 42;
+  a4 = 43;
+  a5.a0[0][0][0] = 44;
+  a5.a0[0][0][1] = 45;
+  a5.a0[0][1][0] = 46;
+  a5.a0[0][1][1] = 47;
+  a5.a0[1][0][0] = 48;
+  a5.a0[1][0][1] = 49;
+  a5.a0[1][1][0] = 50;
+  a5.a0[1][1][1] = 51;
+  a6 = 52;
+
+  std::cout << "Calling TestPassUint8Struct32BytesInlineArrayMultiDimensionalI("
+            << "(" << static_cast<int>(a0) << ", ([[[[["
+            << static_cast<int>(a1.a0[0][0][0][0][0]) << ", "
+            << static_cast<int>(a1.a0[0][0][0][0][1]) << "], ["
+            << static_cast<int>(a1.a0[0][0][0][1][0]) << ", "
+            << static_cast<int>(a1.a0[0][0][0][1][1]) << "]], [["
+            << static_cast<int>(a1.a0[0][0][1][0][0]) << ", "
+            << static_cast<int>(a1.a0[0][0][1][0][1]) << "], ["
+            << static_cast<int>(a1.a0[0][0][1][1][0]) << ", "
+            << static_cast<int>(a1.a0[0][0][1][1][1]) << "]]], [[["
+            << static_cast<int>(a1.a0[0][1][0][0][0]) << ", "
+            << static_cast<int>(a1.a0[0][1][0][0][1]) << "], ["
+            << static_cast<int>(a1.a0[0][1][0][1][0]) << ", "
+            << static_cast<int>(a1.a0[0][1][0][1][1]) << "]], [["
+            << static_cast<int>(a1.a0[0][1][1][0][0]) << ", "
+            << static_cast<int>(a1.a0[0][1][1][0][1]) << "], ["
+            << static_cast<int>(a1.a0[0][1][1][1][0]) << ", "
+            << static_cast<int>(a1.a0[0][1][1][1][1]) << "]]]], [[[["
+            << static_cast<int>(a1.a0[1][0][0][0][0]) << ", "
+            << static_cast<int>(a1.a0[1][0][0][0][1]) << "], ["
+            << static_cast<int>(a1.a0[1][0][0][1][0]) << ", "
+            << static_cast<int>(a1.a0[1][0][0][1][1]) << "]], [["
+            << static_cast<int>(a1.a0[1][0][1][0][0]) << ", "
+            << static_cast<int>(a1.a0[1][0][1][0][1]) << "], ["
+            << static_cast<int>(a1.a0[1][0][1][1][0]) << ", "
+            << static_cast<int>(a1.a0[1][0][1][1][1]) << "]]], [[["
+            << static_cast<int>(a1.a0[1][1][0][0][0]) << ", "
+            << static_cast<int>(a1.a0[1][1][0][0][1]) << "], ["
+            << static_cast<int>(a1.a0[1][1][0][1][0]) << ", "
+            << static_cast<int>(a1.a0[1][1][0][1][1]) << "]], [["
+            << static_cast<int>(a1.a0[1][1][1][0][0]) << ", "
+            << static_cast<int>(a1.a0[1][1][1][0][1]) << "], ["
+            << static_cast<int>(a1.a0[1][1][1][1][0]) << ", "
+            << static_cast<int>(a1.a0[1][1][1][1][1]) << "]]]]]), "
+            << static_cast<int>(a2) << ", ([[["
+            << static_cast<int>(a3.a0[0][0][0]) << ", "
+            << static_cast<int>(a3.a0[0][0][1]) << "], ["
+            << static_cast<int>(a3.a0[0][1][0]) << ", "
+            << static_cast<int>(a3.a0[0][1][1]) << "]], [["
+            << static_cast<int>(a3.a0[1][0][0]) << ", "
+            << static_cast<int>(a3.a0[1][0][1]) << "], ["
+            << static_cast<int>(a3.a0[1][1][0]) << ", "
+            << static_cast<int>(a3.a0[1][1][1]) << "]]]), "
+            << static_cast<int>(a4) << ", ([[["
+            << static_cast<int>(a5.a0[0][0][0]) << ", "
+            << static_cast<int>(a5.a0[0][0][1]) << "], ["
+            << static_cast<int>(a5.a0[0][1][0]) << ", "
+            << static_cast<int>(a5.a0[0][1][1]) << "]], [["
+            << static_cast<int>(a5.a0[1][0][0]) << ", "
+            << static_cast<int>(a5.a0[1][0][1]) << "], ["
+            << static_cast<int>(a5.a0[1][1][0]) << ", "
+            << static_cast<int>(a5.a0[1][1][1]) << "]]]), "
+            << static_cast<int>(a6) << ")"
+            << ")\n";
+
+  uint32_t result = f(a0, a1, a2, a3, a4, a5, a6);
+
+  std::cout << "result = " << result << "\n";
+
+  CHECK_EQ(1378, result);
+
+  // Pass argument that will make the Dart callback throw.
+  a0 = 42;
+
+  result = f(a0, a1, a2, a3, a4, a5, a6);
+
+  CHECK_EQ(0, result);
+
+  // Pass argument that will make the Dart callback return null.
+  a0 = 84;
+
+  result = f(a0, a1, a2, a3, a4, a5, a6);
+
+  CHECK_EQ(0, result);
+
+  return 0;
+}
+
+// Used for testing structs by value.
+// Test struct in multi dimensional inline array.
+DART_EXPORT intptr_t TestPassUint8Struct4BytesInlineArrayMultiDimensionalIn(
+    // NOLINTNEXTLINE(whitespace/parens)
+    uint32_t (*f)(uint8_t a0,
+                  Struct4BytesInlineArrayMultiDimensionalInt a1,
+                  uint8_t a2)) {
+  uint8_t a0;
+  Struct4BytesInlineArrayMultiDimensionalInt a1;
+  uint8_t a2;
+
+  a0 = 1;
+  a1.a0[0][0].a0 = 2;
+  a1.a0[0][1].a0 = -3;
+  a1.a0[1][0].a0 = 4;
+  a1.a0[1][1].a0 = -5;
+  a2 = 6;
+
+  std::cout << "Calling TestPassUint8Struct4BytesInlineArrayMultiDimensionalIn("
+            << "(" << static_cast<int>(a0) << ", ([[("
+            << static_cast<int>(a1.a0[0][0].a0) << "), ("
+            << static_cast<int>(a1.a0[0][1].a0) << ")], [("
+            << static_cast<int>(a1.a0[1][0].a0) << "), ("
+            << static_cast<int>(a1.a0[1][1].a0) << ")]]), "
+            << static_cast<int>(a2) << ")"
+            << ")\n";
+
+  uint32_t result = f(a0, a1, a2);
+
+  std::cout << "result = " << result << "\n";
+
+  CHECK_EQ(5, result);
+
+  // Pass argument that will make the Dart callback throw.
+  a0 = 42;
+
+  result = f(a0, a1, a2);
+
+  CHECK_EQ(0, result);
+
+  // Pass argument that will make the Dart callback return null.
+  a0 = 84;
+
+  result = f(a0, a1, a2);
+
+  CHECK_EQ(0, result);
+
+  return 0;
+}
+
+// Used for testing structs by value.
 // Smallest struct with data.
 DART_EXPORT intptr_t TestReturnStruct1ByteInt(
     // NOLINTNEXTLINE(whitespace/parens)
diff --git a/runtime/tests/vm/dart/regress_45207_test.dart b/runtime/tests/vm/dart/regress_45207_test.dart
new file mode 100644
index 0000000..9f01648
--- /dev/null
+++ b/runtime/tests/vm/dart/regress_45207_test.dart
@@ -0,0 +1,22 @@
+// 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.
+
+// VMOptions=--deterministic  --optimization-counter-threshold=100 --deoptimize-on-runtime-call-name-filter=TypeCheck --deoptimize-on-runtime-call-every=1 --max-subtype-cache-entries=0
+
+main() {
+  void nop() {}
+  for (int i = 0; i < 1000; ++i) {
+    if (assertAssignable(nop) != 1) {
+      throw 'broken';
+    }
+  }
+}
+
+@pragma('vm:never-inline')
+int assertAssignable(dynamic a0) {
+  return ensureValidExpressionStack(1, a0 as void Function());
+}
+
+@pragma('vm:never-inline')
+int ensureValidExpressionStack(int b, void Function() a) => b;
diff --git a/runtime/tests/vm/dart_2/regress_45207_test.dart b/runtime/tests/vm/dart_2/regress_45207_test.dart
new file mode 100644
index 0000000..9f01648
--- /dev/null
+++ b/runtime/tests/vm/dart_2/regress_45207_test.dart
@@ -0,0 +1,22 @@
+// 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.
+
+// VMOptions=--deterministic  --optimization-counter-threshold=100 --deoptimize-on-runtime-call-name-filter=TypeCheck --deoptimize-on-runtime-call-every=1 --max-subtype-cache-entries=0
+
+main() {
+  void nop() {}
+  for (int i = 0; i < 1000; ++i) {
+    if (assertAssignable(nop) != 1) {
+      throw 'broken';
+    }
+  }
+}
+
+@pragma('vm:never-inline')
+int assertAssignable(dynamic a0) {
+  return ensureValidExpressionStack(1, a0 as void Function());
+}
+
+@pragma('vm:never-inline')
+int ensureValidExpressionStack(int b, void Function() a) => b;
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc
index f5423f3c..20440a1 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc
@@ -508,7 +508,7 @@
     // deoptimization point in optimized code, after call.
     const intptr_t deopt_id_after = DeoptId::ToDeoptAfter(deopt_id);
     if (is_optimizing()) {
-      AddDeoptIndexAtCall(deopt_id_after);
+      AddDeoptIndexAtCall(deopt_id_after, env);
     } else {
       // Add deoptimization continuation point after the call and before the
       // arguments are removed.
@@ -902,14 +902,18 @@
   dispatch_table_call_targets_.Add(selector);
 }
 
-CompilerDeoptInfo* FlowGraphCompiler::AddDeoptIndexAtCall(intptr_t deopt_id) {
+CompilerDeoptInfo* FlowGraphCompiler::AddDeoptIndexAtCall(intptr_t deopt_id,
+                                                          Environment* env) {
   ASSERT(is_optimizing());
   ASSERT(!intrinsic_mode());
   ASSERT(!FLAG_precompiled_mode);
+  if (env == nullptr) {
+    env = pending_deoptimization_env_;
+  }
   CompilerDeoptInfo* info =
       new (zone()) CompilerDeoptInfo(deopt_id, ICData::kDeoptAtCall,
                                      0,  // No flags.
-                                     pending_deoptimization_env_);
+                                     env);
   info->set_pc_offset(assembler()->CodeSize());
   deopt_infos_.Add(info);
   return info;
@@ -2859,7 +2863,8 @@
     const InstructionSource& source,
     intptr_t deopt_id,
     const String& dst_name,
-    LocationSummary* locs) {
+    LocationSummary* locs,
+    bool was_licm_hoisted) {
   ASSERT(!source.token_pos.IsClassifying());
   ASSERT(CheckAssertAssignableTypeTestingABILocations(*locs));
 
@@ -2890,7 +2895,8 @@
     }
   }
 
-  GenerateTTSCall(source, deopt_id, type_reg, dst_type, dst_name, locs);
+  GenerateTTSCall(source, deopt_id, type_reg, dst_type, dst_name, locs,
+                  was_licm_hoisted);
   __ Bind(&done);
 }
 
@@ -2902,7 +2908,8 @@
                                         Register reg_with_type,
                                         const AbstractType& dst_type,
                                         const String& dst_name,
-                                        LocationSummary* locs) {
+                                        LocationSummary* locs,
+                                        bool was_licm_hoisted) {
   ASSERT(!dst_name.IsNull());
   // We use 2 consecutive entries in the pool for the subtype cache and the
   // destination name.  The second entry, namely [dst_name] seems to be unused,
@@ -2929,7 +2936,22 @@
   } else {
     GenerateIndirectTTSCall(assembler(), reg_with_type, sub_type_cache_index);
   }
-  EmitCallsiteMetadata(source, deopt_id, UntaggedPcDescriptors::kOther, locs);
+
+  // Lazy deopt to after the call should not have the inputs to AssertAssignable
+  // because those are poped before doing the call.
+  auto pruned_env = pending_deoptimization_env_;
+  if (pruned_env != nullptr) {
+    // If the AssertAssignable was licm hoisted, a lazy-deopt will not continue
+    // after the TTS call inside the assert assignable. Rather the lazy-deopt
+    // will continue at the instruction it was hoisted above (e.g. continue at a
+    // Goto branch) and will later on re-do the AssertAssignable (again).
+    if (!was_licm_hoisted) {
+      pruned_env = pruned_env->DeepCopy(
+          zone(), pruned_env->Length() - AssertAssignableInstr::kNumInputs);
+    }
+  }
+  EmitCallsiteMetadata(source, deopt_id, UntaggedPcDescriptors::kOther, locs,
+                       pruned_env);
 }
 
 // Optimize assignable type check by adding inlined tests for:
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.h b/runtime/vm/compiler/backend/flow_graph_compiler.h
index f3f422e..d3420af 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.h
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.h
@@ -592,7 +592,8 @@
                                 const InstructionSource& source,
                                 intptr_t deopt_id,
                                 const String& dst_name,
-                                LocationSummary* locs);
+                                LocationSummary* locs,
+                                bool was_licm_hoisted);
 
 #if !defined(TARGET_ARCH_IA32)
   void GenerateCallerChecksForAssertAssignable(CompileType* receiver_type,
@@ -604,7 +605,8 @@
                        Register reg_with_type,
                        const AbstractType& dst_type,
                        const String& dst_name,
-                       LocationSummary* locs);
+                       LocationSummary* locs,
+                       bool was_licm_hoisted);
 
   static void GenerateIndirectTTSCall(compiler::Assembler* assembler,
                                       Register reg_with_type,
@@ -862,7 +864,8 @@
                                 ICData::DeoptReasonId reason,
                                 uint32_t flags = 0);
 
-  CompilerDeoptInfo* AddDeoptIndexAtCall(intptr_t deopt_id);
+  CompilerDeoptInfo* AddDeoptIndexAtCall(intptr_t deopt_id,
+                                         Environment* env = nullptr);
   CompilerDeoptInfo* AddSlowPathDeoptInfo(intptr_t deopt_id, Environment* env);
 
   void AddSlowPathCode(SlowPathCode* slow_path);
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
index d89ec9a..a4a2c64 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
@@ -339,7 +339,8 @@
     const InstructionSource& source,
     intptr_t deopt_id,
     const String& dst_name,
-    LocationSummary* locs) {
+    LocationSummary* locs,
+    bool was_licm_hoisted) {
   ASSERT(!source.token_pos.IsClassifying());
   ASSERT(CheckAssertAssignableTypeTestingABILocations(*locs));
 
@@ -395,26 +396,33 @@
   }
 
   __ Bind(&runtime_call);
-  __ PushObject(Object::null_object());            // Make room for the result.
-  __ pushl(TypeTestABI::kInstanceReg);             // Push the source object.
-  // Push the type of the destination.
+
+  // We push the inputs of [AssertAssignable] in the same order as they lie on
+  // the stack in unoptimized code.
+  // That will make the deopt environment we emit as metadata correct and
+  // doesn't need pruning (as in other architectures).
+
+  static_assert(AssertAssignableInstr::kNumInputs == 4,
+                "Expected AssertAssignable to have 4 inputs");
+
+  __ PushRegister(TypeTestABI::kInstanceReg);
   if (!dst_type.IsNull()) {
     __ PushObject(dst_type);
   } else {
-    __ pushl(TypeTestABI::kDstTypeReg);
+    __ PushRegister(TypeTestABI::kDstTypeReg);
   }
-  __ pushl(TypeTestABI::kInstantiatorTypeArgumentsReg);
-  __ pushl(TypeTestABI::kFunctionTypeArgumentsReg);
-  __ PushObject(dst_name);  // Push the name of the destination.
-  // Can reuse kInstanceReg as scratch here since it was pushed above.
-  __ LoadObject(TypeTestABI::kInstanceReg, test_cache);
-  __ pushl(TypeTestABI::kInstanceReg);
-  __ PushObject(Smi::ZoneHandle(zone(), Smi::New(kTypeCheckFromInline)));
-  GenerateRuntimeCall(source, 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);
-  __ popl(TypeTestABI::kInstanceReg);
+  __ PushRegister(TypeTestABI::kInstantiatorTypeArgumentsReg);
+  __ PushRegister(TypeTestABI::kFunctionTypeArgumentsReg);
+
+  // Pass destination name and subtype test reg as register arguments.
+  __ LoadObject(AssertAssignableStubABI::kDstNameReg, dst_name);
+  __ LoadObject(AssertAssignableStubABI::kSubtypeTestReg, test_cache);
+
+  GenerateStubCall(source, StubCode::AssertAssignable(),
+                   UntaggedPcDescriptors::kOther, locs, deopt_id);
+
+  __ Drop(AssertAssignableInstr::kNumInputs - 1);
+  __ PopRegister(TypeTestABI::kInstanceReg);
 
   __ Bind(&is_assignable);
 }
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index 441120c..2759abd 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -5387,7 +5387,7 @@
 
 void AssertAssignableInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
   compiler->GenerateAssertAssignable(value()->Type(), source(), deopt_id(),
-                                     dst_name(), locs());
+                                     dst_name(), locs(), licm_hoisted());
   ASSERT(locs()->in(kInstancePos).reg() == locs()->out(0).reg());
 }
 
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index a09d32c..38006b2 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -3701,6 +3701,7 @@
     kDstTypePos = 1,
     kInstantiatorTAVPos = 2,
     kFunctionTAVPos = 3,
+    kNumInputs = 4,
   };
 
   AssertAssignableInstr(const InstructionSource& source,
@@ -3752,6 +3753,9 @@
 
   virtual bool AttributesEqual(Instruction* other) const { return true; }
 
+  void set_licm_hoisted(bool value) { licm_hoisted_ = value; }
+  bool licm_hoisted() const { return licm_hoisted_; }
+
   virtual Value* RedefinedValue() const;
 
   PRINT_OPERANDS_TO_SUPPORT
@@ -3761,6 +3765,7 @@
   const TokenPosition token_pos_;
   const String& dst_name_;
   const Kind kind_;
+  bool licm_hoisted_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(AssertAssignableInstr);
 };
diff --git a/runtime/vm/compiler/backend/redundancy_elimination.cc b/runtime/vm/compiler/backend/redundancy_elimination.cc
index d63933e..6ecbbf7 100644
--- a/runtime/vm/compiler/backend/redundancy_elimination.cc
+++ b/runtime/vm/compiler/backend/redundancy_elimination.cc
@@ -1336,20 +1336,22 @@
 void LICM::Hoist(ForwardInstructionIterator* it,
                  BlockEntryInstr* pre_header,
                  Instruction* current) {
-  if (current->IsCheckClass()) {
-    current->AsCheckClass()->set_licm_hoisted(true);
-  } else if (current->IsCheckSmi()) {
-    current->AsCheckSmi()->set_licm_hoisted(true);
-  } else if (current->IsCheckEitherNonSmi()) {
-    current->AsCheckEitherNonSmi()->set_licm_hoisted(true);
-  } else if (current->IsCheckArrayBound()) {
+  if (auto check = current->AsCheckClass()) {
+    check->set_licm_hoisted(true);
+  } else if (auto check = current->AsCheckSmi()) {
+    check->set_licm_hoisted(true);
+  } else if (auto check = current->AsCheckEitherNonSmi()) {
+    check->set_licm_hoisted(true);
+  } else if (auto check = current->AsCheckArrayBound()) {
     ASSERT(!CompilerState::Current().is_aot());  // speculative in JIT only
-    current->AsCheckArrayBound()->set_licm_hoisted(true);
-  } else if (current->IsGenericCheckBound()) {
+    check->set_licm_hoisted(true);
+  } else if (auto check = current->AsGenericCheckBound()) {
     ASSERT(CompilerState::Current().is_aot());  // non-speculative in AOT only
     // Does not deopt, so no need for licm_hoisted flag.
-  } else if (current->IsTestCids()) {
-    current->AsTestCids()->set_licm_hoisted(true);
+  } else if (auto check = current->AsTestCids()) {
+    check->set_licm_hoisted(true);
+  } else if (auto check = current->AsAssertAssignable()) {
+    check->set_licm_hoisted(true);
   }
   if (FLAG_trace_optimization) {
     THR_Print("Hoisting instruction %s:%" Pd " from B%" Pd " to B%" Pd "\n",
diff --git a/runtime/vm/compiler/frontend/constant_reader.cc b/runtime/vm/compiler/frontend/constant_reader.cc
index f968c45..7c0d3ad 100644
--- a/runtime/vm/compiler/frontend/constant_reader.cc
+++ b/runtime/vm/compiler/frontend/constant_reader.cc
@@ -264,7 +264,8 @@
       Field& field = Field::Handle(Z);
       Instance& constant = Instance::Handle(Z);
       for (intptr_t j = 0; j < number_of_fields; ++j) {
-        field = H.LookupFieldByKernelField(reader.ReadCanonicalNameReference());
+        field = H.LookupFieldByKernelGetterOrSetter(
+            reader.ReadCanonicalNameReference());
         // Recurse into lazily evaluating all "sub" constants
         // needed to evaluate the current constant.
         const intptr_t entry_offset = reader.ReadUInt();
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index e3708c8..d90c132 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -238,7 +238,7 @@
         ReadBool();
         const NameIndex field_name = ReadCanonicalNameReference();
         const Field& field =
-            Field::Handle(Z, H.LookupFieldByKernelField(field_name));
+            Field::Handle(Z, H.LookupFieldByKernelGetterOrSetter(field_name));
         initializer_fields[i] = &field;
         SkipExpression();
         continue;
@@ -2181,8 +2181,7 @@
   const Function* tearoff_interface_target = &Function::null_function();
   const NameIndex itarget_name =
       ReadInterfaceMemberNameReference();  // read interface_target_reference.
-  if (!H.IsRoot(itarget_name) &&
-      (H.IsGetter(itarget_name) || H.IsField(itarget_name))) {
+  if (!H.IsRoot(itarget_name) && H.IsGetter(itarget_name)) {
     interface_target = &Function::ZoneHandle(
         Z,
         H.LookupMethodByMember(itarget_name, H.DartGetterName(itarget_name)));
@@ -2550,10 +2549,11 @@
       inferred_type_metadata_helper_.GetInferredType(offset);
 
   NameIndex target = ReadCanonicalNameReference();  // read target_reference.
+  ASSERT(H.IsGetter(target));
 
-  if (H.IsField(target)) {
-    const Field& field =
-        Field::ZoneHandle(Z, H.LookupFieldByKernelField(target));
+  const Field& field = Field::ZoneHandle(
+      Z, H.LookupFieldByKernelGetterOrSetter(target, /*required=*/false));
+  if (!field.IsNull()) {
     if (field.is_const()) {
       // Since the CFE inlines all references to const variables and fields,
       // it never emits a StaticGet of a const field.
@@ -2606,44 +2606,39 @@
   if (p != NULL) *p = position;
 
   NameIndex target = ReadCanonicalNameReference();  // read target_reference.
+  ASSERT(H.IsSetter(target));
 
-  if (H.IsField(target)) {
-    const Field& field =
-        Field::ZoneHandle(Z, H.LookupFieldByKernelField(target));
-    const Class& owner = Class::Handle(Z, field.Owner());
-    const String& setter_name = H.DartSetterName(target);
-    const Function& setter =
-        Function::ZoneHandle(Z, owner.LookupStaticFunction(setter_name));
-    Fragment instructions = BuildExpression();  // read expression.
-    if (NeedsDebugStepCheck(stack(), position)) {
-      instructions = DebugStepCheck(position) + instructions;
-    }
-    LocalVariable* variable = MakeTemporary();
-    instructions += LoadLocal(variable);
-    if (!setter.IsNull() && field.NeedsSetter()) {
-      instructions += StaticCall(position, setter, 1, ICData::kStatic);
-      instructions += Drop();
-    } else {
-      instructions += StoreStaticField(position, field);
-    }
-    return instructions;
-  } else {
-    ASSERT(H.IsProcedure(target));
+  // Evaluate the expression on the right hand side.
+  Fragment instructions = BuildExpression();  // read expression.
 
-    // Evaluate the expression on the right hand side.
-    Fragment instructions = BuildExpression();  // read expression.
+  // Look up the target as a setter first and, if not present, as a field
+  // second. This order is needed to avoid looking up a final field as the
+  // target.
+  const Function& function = Function::ZoneHandle(
+      Z, H.LookupStaticMethodByKernelProcedure(target, /*required=*/false));
+
+  if (!function.IsNull()) {
     LocalVariable* variable = MakeTemporary();
 
     // Prepare argument.
     instructions += LoadLocal(variable);
 
     // Invoke the setter function.
-    const Function& function =
-        Function::ZoneHandle(Z, H.LookupStaticMethodByKernelProcedure(target));
     instructions += StaticCall(position, function, 1, ICData::kStatic);
 
     // Drop the unused result & leave the stored value on the stack.
     return instructions + Drop();
+  } else {
+    const Field& field =
+        Field::ZoneHandle(Z, H.LookupFieldByKernelGetterOrSetter(target));
+    ASSERT(!field.NeedsSetter());
+    if (NeedsDebugStepCheck(stack(), position)) {
+      instructions = DebugStepCheck(position) + instructions;
+    }
+    LocalVariable* variable = MakeTemporary();
+    instructions += LoadLocal(variable);
+    instructions += StoreStaticField(position, field);
+    return instructions;
   }
 }
 
@@ -2771,8 +2766,7 @@
   // TODO(dartbug.com/34497): Once front-end desugars calls via
   // fields/getters, filtering of field and getter interface targets here
   // can be turned into assertions.
-  if (!H.IsRoot(itarget_name) && !H.IsField(itarget_name) &&
-      !H.IsGetter(itarget_name)) {
+  if (!H.IsRoot(itarget_name) && !H.IsGetter(itarget_name)) {
     interface_target = &Function::ZoneHandle(
         Z, H.LookupMethodByMember(itarget_name,
                                   H.DartProcedureName(itarget_name)));
diff --git a/runtime/vm/compiler/frontend/kernel_fingerprints.cc b/runtime/vm/compiler/frontend/kernel_fingerprints.cc
index d8cacea..ca9ebbf 100644
--- a/runtime/vm/compiler/frontend/kernel_fingerprints.cc
+++ b/runtime/vm/compiler/frontend/kernel_fingerprints.cc
@@ -322,7 +322,7 @@
 
 void KernelFingerprintHelper::CalculateGetterNameFingerprint() {
   const NameIndex name = ReadCanonicalNameReference();
-  if (!H.IsRoot(name) && (H.IsGetter(name) || H.IsField(name))) {
+  if (!H.IsRoot(name) && H.IsGetter(name)) {
     BuildHash(H.DartGetterName(name).Hash());
   }
   ReadCanonicalNameReference();  // read interface_target_origin_reference
@@ -339,7 +339,7 @@
 void KernelFingerprintHelper::CalculateMethodNameFingerprint() {
   const NameIndex name =
       ReadCanonicalNameReference();  // read interface_target_reference.
-  if (!H.IsRoot(name) && !H.IsField(name)) {
+  if (!H.IsRoot(name)) {
     BuildHash(H.DartProcedureName(name).Hash());
   }
   ReadCanonicalNameReference();  // read interface_target_origin_reference
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.cc b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
index 7324e81..5078854 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.cc
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.cc
@@ -235,22 +235,7 @@
 }
 
 bool TranslationHelper::IsMember(NameIndex name) {
-  return IsConstructor(name) || IsField(name) || IsProcedure(name);
-}
-
-bool TranslationHelper::IsField(NameIndex name) {
-  // Fields with private names have the import URI of the library where they are
-  // visible as the parent and the string "@fields" as the parent's parent.
-  // Fields with non-private names have the string "@fields' as the parent.
-  if (IsRoot(name)) {
-    return false;
-  }
-  NameIndex kind = CanonicalNameParent(name);
-  if (IsPrivate(name)) {
-    kind = CanonicalNameParent(kind);
-  }
-  return StringEquals(CanonicalNameString(kind), "@fields") ||
-         StringEquals(CanonicalNameString(kind), "@=fields");
+  return IsConstructor(name) || IsProcedure(name);
 }
 
 bool TranslationHelper::IsConstructor(NameIndex name) {
@@ -330,7 +315,7 @@
 }
 
 NameIndex TranslationHelper::EnclosingName(NameIndex name) {
-  ASSERT(IsField(name) || IsConstructor(name) || IsProcedure(name));
+  ASSERT(IsConstructor(name) || IsProcedure(name));
   NameIndex enclosing = CanonicalNameParent(CanonicalNameParent(name));
   if (IsPrivate(name)) {
     enclosing = CanonicalNameParent(enclosing);
@@ -587,8 +572,10 @@
   return info_.InsertClass(thread_, name_index_handle_, klass);
 }
 
-FieldPtr TranslationHelper::LookupFieldByKernelField(NameIndex kernel_field) {
-  ASSERT(IsField(kernel_field));
+FieldPtr TranslationHelper::LookupFieldByKernelGetterOrSetter(
+    NameIndex kernel_field,
+    bool required) {
+  ASSERT(IsGetter(kernel_field) || IsSetter(kernel_field));
   NameIndex enclosing = EnclosingName(kernel_field);
 
   Class& klass = Class::Handle(Z);
@@ -604,12 +591,15 @@
   Field& field = Field::Handle(
       Z, klass.LookupFieldAllowPrivate(
              DartSymbolObfuscate(CanonicalNameString(kernel_field))));
-  CheckStaticLookup(field);
+  if (required) {
+    CheckStaticLookup(field);
+  }
   return field.ptr();
 }
 
 FunctionPtr TranslationHelper::LookupStaticMethodByKernelProcedure(
-    NameIndex procedure) {
+    NameIndex procedure,
+    bool required) {
   const String& procedure_name = DartProcedureName(procedure);
 
   // The parent is either a library or a class (in which case the procedure is a
@@ -620,7 +610,9 @@
         Library::Handle(Z, LookupLibraryByKernelLibrary(enclosing));
     Function& function =
         Function::Handle(Z, library.LookupFunctionAllowPrivate(procedure_name));
-    CheckStaticLookup(function);
+    if (required) {
+      CheckStaticLookup(function);
+    }
     return function.ptr();
   } else {
     ASSERT(IsClass(enclosing));
@@ -629,7 +621,9 @@
     ASSERT(error == Error::null());
     Function& function = Function::ZoneHandle(
         Z, klass.LookupFunctionAllowPrivate(procedure_name));
-    CheckStaticLookup(function);
+    if (required) {
+      CheckStaticLookup(function);
+    }
     return function.ptr();
   }
 }
diff --git a/runtime/vm/compiler/frontend/kernel_translation_helper.h b/runtime/vm/compiler/frontend/kernel_translation_helper.h
index b76603a..d5d39fe 100644
--- a/runtime/vm/compiler/frontend/kernel_translation_helper.h
+++ b/runtime/vm/compiler/frontend/kernel_translation_helper.h
@@ -101,7 +101,6 @@
   bool IsLibrary(NameIndex name);
   bool IsClass(NameIndex name);
   bool IsMember(NameIndex name);
-  bool IsField(NameIndex name);
   bool IsConstructor(NameIndex name);
   bool IsProcedure(NameIndex name);
   bool IsMethod(NameIndex name);
@@ -164,8 +163,10 @@
   virtual LibraryPtr LookupLibraryByKernelLibrary(NameIndex library);
   virtual ClassPtr LookupClassByKernelClass(NameIndex klass);
 
-  FieldPtr LookupFieldByKernelField(NameIndex field);
-  FunctionPtr LookupStaticMethodByKernelProcedure(NameIndex procedure);
+  FieldPtr LookupFieldByKernelGetterOrSetter(NameIndex field,
+                                             bool required = true);
+  FunctionPtr LookupStaticMethodByKernelProcedure(NameIndex procedure,
+                                                  bool required = true);
   FunctionPtr LookupConstructorByKernelConstructor(NameIndex constructor);
   FunctionPtr LookupConstructorByKernelConstructor(const Class& owner,
                                                    NameIndex constructor);
diff --git a/runtime/vm/compiler/stub_code_compiler.cc b/runtime/vm/compiler/stub_code_compiler.cc
index c4ed124..3478028 100644
--- a/runtime/vm/compiler/stub_code_compiler.cc
+++ b/runtime/vm/compiler/stub_code_compiler.cc
@@ -179,6 +179,31 @@
   __ Ret();
 }
 
+void StubCodeCompiler::GenerateAssertAssignableStub(Assembler* assembler) {
+#if !defined(TARGET_ARCH_IA32)
+  __ Breakpoint();
+#else
+  __ EnterStubFrame();
+  __ PushObject(Object::null_object());  // Make room for the result.
+  __ pushl(Address(
+      EBP, target::kWordSize * AssertAssignableStubABI::kInstanceSlotFromFp));
+  __ pushl(Address(
+      EBP, target::kWordSize * AssertAssignableStubABI::kDstTypeSlotFromFp));
+  __ pushl(Address(
+      EBP,
+      target::kWordSize * AssertAssignableStubABI::kInstantiatorTAVSlotFromFp));
+  __ pushl(Address(EBP, target::kWordSize *
+                            AssertAssignableStubABI::kFunctionTAVSlotFromFp));
+  __ PushRegister(AssertAssignableStubABI::kDstNameReg);
+  __ PushRegister(AssertAssignableStubABI::kSubtypeTestReg);
+  __ PushObject(Smi::ZoneHandle(Smi::New(kTypeCheckFromInline)));
+  __ CallRuntime(kTypeCheckRuntimeEntry, /*argument_count=*/7);
+  __ Drop(8);
+  __ LeaveStubFrame();
+  __ Ret();
+#endif
+}
+
 void StubCodeCompiler::GenerateInstanceOfStub(Assembler* assembler) {
   __ EnterStubFrame();
   __ PushObject(NullObject());  // Make room for the result.
diff --git a/runtime/vm/constants_ia32.h b/runtime/vm/constants_ia32.h
index da4d058..ca05271 100644
--- a/runtime/vm/constants_ia32.h
+++ b/runtime/vm/constants_ia32.h
@@ -138,6 +138,17 @@
   // (throws if the subtype check fails).
 };
 
+// For calling the ia32-specific AssertAssignableStub
+struct AssertAssignableStubABI {
+  static const Register kDstNameReg = EBX;
+  static const Register kSubtypeTestReg = ECX;
+
+  static const intptr_t kInstanceSlotFromFp = 2 + 3;
+  static const intptr_t kDstTypeSlotFromFp = 2 + 2;
+  static const intptr_t kInstantiatorTAVSlotFromFp = 2 + 1;
+  static const intptr_t kFunctionTAVSlotFromFp = 2 + 0;
+};
+
 // ABI for InitStaticFieldStub.
 struct InitStaticFieldABI {
   static const Register kFieldReg = EAX;
diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h
index c118e0f..59226e1 100644
--- a/runtime/vm/flag_list.h
+++ b/runtime/vm/flag_list.h
@@ -110,6 +110,8 @@
     "Deoptimizes we are about to return to Dart code from native entries.")    \
   C(deoptimize_every, 0, 0, int, 0,                                            \
     "Deoptimize on every N stack overflow checks")                             \
+  P(deoptimize_on_runtime_call_every, int, 0,                                  \
+    "Deoptimize functions on every runtime call.")                             \
   R(disable_alloc_stubs_after_gc, false, bool, false, "Stress testing flag.")  \
   R(dump_megamorphic_stats, false, bool, false,                                \
     "Dump megamorphic cache statistics")                                       \
diff --git a/runtime/vm/kernel_binary.h b/runtime/vm/kernel_binary.h
index 1c29a5b..6a4d1b1 100644
--- a/runtime/vm/kernel_binary.h
+++ b/runtime/vm/kernel_binary.h
@@ -20,8 +20,8 @@
 static const uint32_t kMagicProgramFile = 0x90ABCDEFu;
 
 // Both version numbers are inclusive.
-static const uint32_t kMinSupportedKernelFormatVersion = 57;
-static const uint32_t kMaxSupportedKernelFormatVersion = 57;
+static const uint32_t kMinSupportedKernelFormatVersion = 58;
+static const uint32_t kMaxSupportedKernelFormatVersion = 58;
 
 // Keep in sync with package:kernel/lib/binary/tag.dart
 #define KERNEL_TAG_LIST(V)                                                     \
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 8fb8e14..4d42361 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -10180,7 +10180,9 @@
   // This assertion ensures that the cid seen by the background compiler is
   // consistent. So the assertion passes if the field is a clone. It also
   // passes if the field is static, because we don't use field guards on
-  // static fields.
+  // static fields. It also passes if we're compiling unoptimized
+  // code (in which case the caller might get different answers if it obtains
+  // the guarded cid multiple times).
   Thread* thread = Thread::Current();
   ASSERT(!thread->IsInsideCompiler() ||
 #if !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index ce1222e..f38ceba 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -3653,7 +3653,7 @@
 
 #define DEFINE_ACCESSORS(name, accessor_name)                                  \
   void set_##accessor_name(bool value) const {                                 \
-    untag()->kind_tag_.UpdateBool<name##Bit>(value);                           \
+    untag()->kind_tag_.UpdateUnsynchronized<name##Bit>(value);                 \
   }                                                                            \
   bool accessor_name() const { return untag()->kind_tag_.Read<name##Bit>(); }
   FOR_EACH_FUNCTION_KIND_BIT(DEFINE_ACCESSORS)
@@ -3661,7 +3661,7 @@
 
 #define DEFINE_ACCESSORS(name, accessor_name)                                  \
   void set_##accessor_name(bool value) const {                                 \
-    untag()->kind_tag_.UpdateUnsynchronized<name##Bit>(value);                 \
+    untag()->kind_tag_.UpdateBool<name##Bit>(value);                           \
   }                                                                            \
   bool accessor_name() const { return untag()->kind_tag_.Read<name##Bit>(); }
   FOR_EACH_FUNCTION_VOLATILE_KIND_BIT(DEFINE_ACCESSORS)
@@ -4103,7 +4103,8 @@
     set_guarded_cid_unsafe(cid);
   }
   void set_guarded_cid_unsafe(intptr_t cid) const {
-    StoreNonPointer(&untag()->guarded_cid_, cid);
+    StoreNonPointer<ClassIdTagType, ClassIdTagType, std::memory_order_relaxed>(
+        &untag()->guarded_cid_, cid);
   }
   static intptr_t guarded_cid_offset() {
     return OFFSET_OF(UntaggedField, guarded_cid_);
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index b3325b0..39d4e2d 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -80,6 +80,10 @@
             deoptimize_filter,
             NULL,
             "Deoptimize in named function on stack overflow checks");
+DEFINE_FLAG(charp,
+            deoptimize_on_runtime_call_name_filter,
+            NULL,
+            "Runtime call name filter for --deoptimize-on-runtime-call-every.");
 
 DEFINE_FLAG(bool,
             unopt_monomorphic_calls,
@@ -3205,6 +3209,29 @@
   UNREACHABLE();
 }
 
+void OnEveryRuntimeEntryCall(Thread* thread, const char* runtime_call_name) {
+  ASSERT(FLAG_deoptimize_on_runtime_call_every > 0);
+  if (FLAG_precompiled_mode) {
+    return;
+  }
+  if (IsolateGroup::IsSystemIsolateGroup(thread->isolate_group())) {
+    return;
+  }
+  const bool is_deopt_related = strstr(runtime_call_name, "Deoptimize") != 0;
+  if (is_deopt_related) {
+    return;
+  }
+  if (FLAG_deoptimize_on_runtime_call_name_filter != nullptr &&
+      strstr(runtime_call_name, FLAG_deoptimize_on_runtime_call_name_filter) ==
+          0) {
+    return;
+  }
+  const uint32_t count = thread->IncrementAndGetRuntimeCallCount();
+  if ((count % FLAG_deoptimize_on_runtime_call_every) == 0) {
+    DeoptimizeFunctionsOnStack();
+  }
+}
+
 double DartModulo(double left, double right) {
   double remainder = fmod_ieee(left, right);
   if (remainder == 0.0) {
diff --git a/runtime/vm/runtime_entry.h b/runtime/vm/runtime_entry.h
index c3468ba..3120254 100644
--- a/runtime/vm/runtime_entry.h
+++ b/runtime/vm/runtime_entry.h
@@ -114,6 +114,9 @@
       StackZone zone(thread);                                                  \
       HANDLESCOPE(thread);                                                     \
       CHECK_SIMULATOR_STACK_OVERFLOW();                                        \
+      if (FLAG_deoptimize_on_runtime_call_every > 0) {                         \
+        OnEveryRuntimeEntryCall(thread, "" #name);                             \
+      }                                                                        \
       DRT_Helper##name(isolate, thread, zone.GetZone(), arguments);            \
     }                                                                          \
   }                                                                            \
@@ -160,6 +163,8 @@
 
 const char* DeoptReasonToCString(ICData::DeoptReasonId deopt_reason);
 
+void OnEveryRuntimeEntryCall(Thread* thread, const char* runtime_call_name);
+
 void DeoptimizeAt(Thread* mutator_thread,
                   const Code& optimized_code,
                   StackFrame* frame);
diff --git a/runtime/vm/stub_code_list.h b/runtime/vm/stub_code_list.h
index d99a661..ea8d907 100644
--- a/runtime/vm/stub_code_list.h
+++ b/runtime/vm/stub_code_list.h
@@ -86,6 +86,7 @@
   V(OneArgUnoptimizedStaticCall)                                               \
   V(TwoArgsUnoptimizedStaticCall)                                              \
   V(AssertSubtype)                                                             \
+  V(AssertAssignable)                                                          \
   V(TypeIsTopTypeForSubtyping)                                                 \
   V(TypeIsTopTypeForSubtypingNullSafe)                                         \
   V(NullIsAssignableToType)                                                    \
diff --git a/runtime/vm/thread.h b/runtime/vm/thread.h
index 5aee930..c76464f 100644
--- a/runtime/vm/thread.h
+++ b/runtime/vm/thread.h
@@ -340,6 +340,8 @@
     return ++stack_overflow_count_;
   }
 
+  uint32_t IncrementAndGetRuntimeCallCount() { return ++runtime_call_count_; }
+
   static uword stack_overflow_shared_stub_entry_point_offset(bool fpu_regs) {
     return fpu_regs
                ? stack_overflow_shared_with_fpu_regs_entry_point_offset()
@@ -1014,6 +1016,7 @@
   uint16_t deferred_interrupts_mask_;
   uint16_t deferred_interrupts_;
   int32_t stack_overflow_count_;
+  uint32_t runtime_call_count_ = 0;
 
   // Deoptimization of stack frames.
   PendingDeopts pending_deopts_;
diff --git a/sdk/lib/_internal/js_runtime/lib/js_number.dart b/sdk/lib/_internal/js_runtime/lib/js_number.dart
index 1d3ad24..d0f0c71 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_number.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_number.dart
@@ -404,9 +404,6 @@
     return _shrOtherPositive(other);
   }
 
-  num operator >>>(num other) =>
-    throw UnimplementedError('int.>>> is not implemented yet');
-
   num _shrOtherPositive(num other) {
     return JS('num', '#', this) > 0
         ? _shrBothPositive(other)
@@ -434,6 +431,17 @@
         : JS('JSUInt32', r'# >>> #', this, other);
   }
 
+  num operator >>>(num other) {
+    if (other is! num) throw argumentErrorValue(other);
+    if (other < 0) throw argumentErrorValue(other);
+    return _shruOtherPositive(other);
+  }
+
+  num _shruOtherPositive(num other) {
+    if (other > 31) return 0;
+    return JS('JSUInt32', r'# >>> #', this, other);
+  }
+
   num operator &(num other) {
     if (other is! num) throw argumentErrorValue(other);
     return JS('JSUInt32', r'(# & #) >>> 0', this, other);
diff --git a/sdk/lib/_internal/vm/lib/ffi_patch.dart b/sdk/lib/_internal/vm/lib/ffi_patch.dart
index 38dbcda..f9612e6 100644
--- a/sdk/lib/_internal/vm/lib/ffi_patch.dart
+++ b/sdk/lib/_internal/vm/lib/ffi_patch.dart
@@ -115,7 +115,17 @@
   final int _size;
 
   @pragma("vm:entry-point")
-  Array._(this._typedDataBase, this._size);
+  final List<int> _nestedDimensions;
+
+  @pragma("vm:entry-point")
+  Array._(this._typedDataBase, this._size, this._nestedDimensions);
+
+  late final int _nestedDimensionsFlattened = _nestedDimensions.fold(
+      1, (accumulator, element) => accumulator * element);
+
+  late final int _nestedDimensionsFirst = _nestedDimensions.first;
+
+  late final List<int> _nestedDimensionsRest = _nestedDimensions.sublist(1);
 
   _checkIndex(int index) {
     if (index < 0 || index >= _size) {
@@ -124,13 +134,35 @@
   }
 
   @patch
-  const factory Array(int dimension1) = _ArraySize<T>;
+  const factory Array(int dimension1,
+      [int dimension2,
+      int dimension3,
+      int dimension4,
+      int dimension5]) = _ArraySize<T>;
+
+  @patch
+  const factory Array.multi(List<int> dimensions) = _ArraySize<T>.multi;
 }
 
 class _ArraySize<T extends NativeType> implements Array<T> {
-  final int dimension1;
+  final int? dimension1;
+  final int? dimension2;
+  final int? dimension3;
+  final int? dimension4;
+  final int? dimension5;
 
-  const _ArraySize(this.dimension1);
+  final List<int>? dimensions;
+
+  const _ArraySize(this.dimension1,
+      [this.dimension2, this.dimension3, this.dimension4, this.dimension5])
+      : dimensions = null;
+
+  const _ArraySize.multi(this.dimensions)
+      : dimension1 = null,
+        dimension2 = null,
+        dimension3 = null,
+        dimension4 = null,
+        dimension5 = null;
 }
 
 /// Returns an integer encoding the ABI used for size and alignment
@@ -706,6 +738,16 @@
       _storePointer(this, _intPtrSize * index, value);
 }
 
+extension ArrayArray<T extends NativeType> on Array<Array<T>> {
+  @patch
+  Array<T> operator [](int index) =>
+      throw "UNREACHABLE: This case should have been rewritten in the CFE.";
+
+  @patch
+  void operator []=(int index, Array<T> value) =>
+      throw "UNREACHABLE: This case should have been rewritten in the CFE.";
+}
+
 extension StructArray<T extends Struct> on Array<T> {
   @patch
   T operator [](int index) {
diff --git a/sdk/lib/ffi/ffi.dart b/sdk/lib/ffi/ffi.dart
index 8510065..c1db8ab 100644
--- a/sdk/lib/ffi/ffi.dart
+++ b/sdk/lib/ffi/ffi.dart
@@ -93,11 +93,30 @@
   /// class MyStruct extends Struct {
   ///   @Array(8)
   ///   external Array<Uint8> inlineArray;
+  ///
+  ///   @Array(2, 2, 2)
+  ///   external Array<Array<Array<Uint8>>> threeDimensionalInlineArray;
   /// }
   /// ```
   ///
   /// Do not invoke in normal code.
-  external const factory Array(int dimension1);
+  external const factory Array(int dimension1,
+      [int dimension2, int dimension3, int dimension4, int dimension5]);
+
+  /// Const constructor to specify [Array] dimensions in [Struct]s.
+  ///
+  /// ```
+  /// class MyStruct extends Struct {
+  ///   @Array.multi([2, 2, 2])
+  ///   external Array<Array<Array<Uint8>>> threeDimensionalInlineArray;
+  ///
+  ///   @Array.multi([2, 2, 2, 2, 2, 2, 2, 2])
+  ///   external Array<Array<Array<Array<Array<Array<Array<Array<Uint8>>>>>>>> eightDimensionalInlineArray;
+  /// }
+  /// ```
+  ///
+  /// Do not invoke in normal code.
+  external const factory Array.multi(List<int> dimensions);
 }
 
 /// Extension on [Pointer] specialized for the type argument [NativeFunction].
@@ -662,6 +681,13 @@
   external T operator [](int index);
 }
 
+/// Bounds checking indexing methods on [Array]s of [Array].
+extension ArrayArray<T extends NativeType> on Array<Array<T>> {
+  external Array<T> operator [](int index);
+
+  external void operator []=(int index, Array<T> value);
+}
+
 /// Extension to retrieve the native `Dart_Port` from a [SendPort].
 extension NativePort on SendPort {
   /// The native port of this [SendPort].
diff --git a/sdk/lib/io/process.dart b/sdk/lib/io/process.dart
index a1ccce6..ccb9b44 100644
--- a/sdk/lib/io/process.dart
+++ b/sdk/lib/io/process.dart
@@ -177,7 +177,7 @@
 /// main() async {
 ///   // List all files in the current directory in UNIX-like systems.
 ///   var result = await Process.run('ls', ['-l']);
-///   print(results.stdout);
+///   print(result.stdout);
 /// }
 /// ```
 /// ## Start a process with the start method
diff --git a/tests/ffi/function_callbacks_structs_by_value_generated_test.dart b/tests/ffi/function_callbacks_structs_by_value_generated_test.dart
index 025b2d6..4cdd74b 100644
--- a/tests/ffi/function_callbacks_structs_by_value_generated_test.dart
+++ b/tests/ffi/function_callbacks_structs_by_value_generated_test.dart
@@ -273,6 +273,18 @@
           passStructStruct16BytesMixed3x10, 0.0),
       passStructStruct16BytesMixed3x10AfterCallback),
   CallbackTest.withCheck(
+      "PassUint8Struct32BytesInlineArrayMultiDimensionalI",
+      Pointer.fromFunction<
+              PassUint8Struct32BytesInlineArrayMultiDimensionalIType>(
+          passUint8Struct32BytesInlineArrayMultiDimensionalI, 0),
+      passUint8Struct32BytesInlineArrayMultiDimensionalIAfterCallback),
+  CallbackTest.withCheck(
+      "PassUint8Struct4BytesInlineArrayMultiDimensionalIn",
+      Pointer.fromFunction<
+              PassUint8Struct4BytesInlineArrayMultiDimensionalInType>(
+          passUint8Struct4BytesInlineArrayMultiDimensionalIn, 0),
+      passUint8Struct4BytesInlineArrayMultiDimensionalInAfterCallback),
+  CallbackTest.withCheck(
       "ReturnStruct1ByteInt",
       Pointer.fromFunction<ReturnStruct1ByteIntType>(returnStruct1ByteInt),
       returnStruct1ByteIntAfterCallback),
@@ -5967,6 +5979,237 @@
   Expect.approxEquals(30.0, result);
 }
 
+typedef PassUint8Struct32BytesInlineArrayMultiDimensionalIType
+    = Uint32 Function(
+        Uint8,
+        Struct32BytesInlineArrayMultiDimensionalInt,
+        Uint8,
+        Struct8BytesInlineArrayMultiDimensionalInt,
+        Uint8,
+        Struct8BytesInlineArrayMultiDimensionalInt,
+        Uint8);
+
+// Global variables to be able to test inputs after callback returned.
+int passUint8Struct32BytesInlineArrayMultiDimensionalI_a0 = 0;
+Struct32BytesInlineArrayMultiDimensionalInt
+    passUint8Struct32BytesInlineArrayMultiDimensionalI_a1 =
+    Struct32BytesInlineArrayMultiDimensionalInt();
+int passUint8Struct32BytesInlineArrayMultiDimensionalI_a2 = 0;
+Struct8BytesInlineArrayMultiDimensionalInt
+    passUint8Struct32BytesInlineArrayMultiDimensionalI_a3 =
+    Struct8BytesInlineArrayMultiDimensionalInt();
+int passUint8Struct32BytesInlineArrayMultiDimensionalI_a4 = 0;
+Struct8BytesInlineArrayMultiDimensionalInt
+    passUint8Struct32BytesInlineArrayMultiDimensionalI_a5 =
+    Struct8BytesInlineArrayMultiDimensionalInt();
+int passUint8Struct32BytesInlineArrayMultiDimensionalI_a6 = 0;
+
+// Result variable also global, so we can delete it after the callback.
+int passUint8Struct32BytesInlineArrayMultiDimensionalIResult = 0;
+
+int passUint8Struct32BytesInlineArrayMultiDimensionalICalculateResult() {
+  int result = 0;
+
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a0;
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][0][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][0][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][0][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][0][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][1][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][1][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][1][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][1][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][0][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][0][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][0][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][0][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][1][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][1][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][1][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][1][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][0][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][0][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][0][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][0][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][1][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][1][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][1][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][1][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][0][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][0][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][0][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][0][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][1][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][1][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][1][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][1][1][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a2;
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[0][0][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[0][0][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[0][1][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[0][1][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[1][0][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[1][0][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[1][1][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[1][1][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a4;
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[0][0][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[0][0][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[0][1][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[0][1][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[1][0][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[1][0][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[1][1][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[1][1][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a6;
+
+  passUint8Struct32BytesInlineArrayMultiDimensionalIResult = result;
+
+  return result;
+}
+
+/// Test multi dimensional inline array struct as argument.
+int passUint8Struct32BytesInlineArrayMultiDimensionalI(
+    int a0,
+    Struct32BytesInlineArrayMultiDimensionalInt a1,
+    int a2,
+    Struct8BytesInlineArrayMultiDimensionalInt a3,
+    int a4,
+    Struct8BytesInlineArrayMultiDimensionalInt a5,
+    int a6) {
+  print(
+      "passUint8Struct32BytesInlineArrayMultiDimensionalI(${a0}, ${a1}, ${a2}, ${a3}, ${a4}, ${a5}, ${a6})");
+
+  // In legacy mode, possibly return null.
+
+  // In both nnbd and legacy mode, possibly throw.
+  if (a0 == 42 || a0 == 84) {
+    print("throwing!");
+    throw Exception(
+        "PassUint8Struct32BytesInlineArrayMultiDimensionalI throwing on purpose!");
+  }
+
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a0 = a0;
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a1 = a1;
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a2 = a2;
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a3 = a3;
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a4 = a4;
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a5 = a5;
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a6 = a6;
+
+  final result =
+      passUint8Struct32BytesInlineArrayMultiDimensionalICalculateResult();
+
+  print("result = $result");
+
+  return result;
+}
+
+void passUint8Struct32BytesInlineArrayMultiDimensionalIAfterCallback() {
+  final result =
+      passUint8Struct32BytesInlineArrayMultiDimensionalICalculateResult();
+
+  print("after callback result = $result");
+
+  Expect.equals(1378, result);
+}
+
+typedef PassUint8Struct4BytesInlineArrayMultiDimensionalInType = Uint32
+    Function(Uint8, Struct4BytesInlineArrayMultiDimensionalInt, Uint8);
+
+// Global variables to be able to test inputs after callback returned.
+int passUint8Struct4BytesInlineArrayMultiDimensionalIn_a0 = 0;
+Struct4BytesInlineArrayMultiDimensionalInt
+    passUint8Struct4BytesInlineArrayMultiDimensionalIn_a1 =
+    Struct4BytesInlineArrayMultiDimensionalInt();
+int passUint8Struct4BytesInlineArrayMultiDimensionalIn_a2 = 0;
+
+// Result variable also global, so we can delete it after the callback.
+int passUint8Struct4BytesInlineArrayMultiDimensionalInResult = 0;
+
+int passUint8Struct4BytesInlineArrayMultiDimensionalInCalculateResult() {
+  int result = 0;
+
+  result += passUint8Struct4BytesInlineArrayMultiDimensionalIn_a0;
+  result += passUint8Struct4BytesInlineArrayMultiDimensionalIn_a1.a0[0][0].a0;
+  result += passUint8Struct4BytesInlineArrayMultiDimensionalIn_a1.a0[0][1].a0;
+  result += passUint8Struct4BytesInlineArrayMultiDimensionalIn_a1.a0[1][0].a0;
+  result += passUint8Struct4BytesInlineArrayMultiDimensionalIn_a1.a0[1][1].a0;
+  result += passUint8Struct4BytesInlineArrayMultiDimensionalIn_a2;
+
+  passUint8Struct4BytesInlineArrayMultiDimensionalInResult = result;
+
+  return result;
+}
+
+/// Test struct in multi dimensional inline array.
+int passUint8Struct4BytesInlineArrayMultiDimensionalIn(
+    int a0, Struct4BytesInlineArrayMultiDimensionalInt a1, int a2) {
+  print(
+      "passUint8Struct4BytesInlineArrayMultiDimensionalIn(${a0}, ${a1}, ${a2})");
+
+  // In legacy mode, possibly return null.
+
+  // In both nnbd and legacy mode, possibly throw.
+  if (a0 == 42 || a0 == 84) {
+    print("throwing!");
+    throw Exception(
+        "PassUint8Struct4BytesInlineArrayMultiDimensionalIn throwing on purpose!");
+  }
+
+  passUint8Struct4BytesInlineArrayMultiDimensionalIn_a0 = a0;
+  passUint8Struct4BytesInlineArrayMultiDimensionalIn_a1 = a1;
+  passUint8Struct4BytesInlineArrayMultiDimensionalIn_a2 = a2;
+
+  final result =
+      passUint8Struct4BytesInlineArrayMultiDimensionalInCalculateResult();
+
+  print("result = $result");
+
+  return result;
+}
+
+void passUint8Struct4BytesInlineArrayMultiDimensionalInAfterCallback() {
+  final result =
+      passUint8Struct4BytesInlineArrayMultiDimensionalInCalculateResult();
+
+  print("after callback result = $result");
+
+  Expect.equals(5, result);
+}
+
 typedef ReturnStruct1ByteIntType = Struct1ByteInt Function(Int8);
 
 // Global variables to be able to test inputs after callback returned.
diff --git a/tests/ffi/function_structs_by_value_generated_test.dart b/tests/ffi/function_structs_by_value_generated_test.dart
index e909559..ef59018 100644
--- a/tests/ffi/function_structs_by_value_generated_test.dart
+++ b/tests/ffi/function_structs_by_value_generated_test.dart
@@ -68,6 +68,8 @@
     testPassStructStruct16BytesHomogeneousFloat2x5();
     testPassStructStruct32BytesHomogeneousDouble2x5();
     testPassStructStruct16BytesMixed3x10();
+    testPassUint8Struct32BytesInlineArrayMultiDimensionalI();
+    testPassUint8Struct4BytesInlineArrayMultiDimensionalIn();
     testReturnStruct1ByteInt();
     testReturnStruct3BytesHomogeneousUint8();
     testReturnStruct3BytesInt2ByteAligned();
@@ -1043,7 +1045,7 @@
   @Array(8)
   external Array<Uint8> a0;
 
-  String toString() => "(${[for (var i = 0; i < 8; i += 1) a0[i]]})";
+  String toString() => "(${[for (var i0 = 0; i0 < 8; i0 += 1) a0[i0]]})";
 }
 
 class StructInlineArrayIrregular extends Struct {
@@ -1053,14 +1055,14 @@
   @Uint8()
   external int a1;
 
-  String toString() => "(${[for (var i = 0; i < 2; i += 1) a0[i]]}, ${a1})";
+  String toString() => "(${[for (var i0 = 0; i0 < 2; i0 += 1) a0[i0]]}, ${a1})";
 }
 
 class StructInlineArray100Bytes extends Struct {
   @Array(100)
   external Array<Uint8> a0;
 
-  String toString() => "(${[for (var i = 0; i < 100; i += 1) a0[i]]})";
+  String toString() => "(${[for (var i0 = 0; i0 < 100; i0 += 1) a0[i0]]})";
 }
 
 class StructInlineArrayBig extends Struct {
@@ -1074,7 +1076,7 @@
   external Array<Uint8> a2;
 
   String toString() =>
-      "(${a0}, ${a1}, ${[for (var i = 0; i < 4000; i += 1) a2[i]]})";
+      "(${a0}, ${a1}, ${[for (var i0 = 0; i0 < 4000; i0 += 1) a2[i0]]})";
 }
 
 class StructStruct16BytesHomogeneousFloat2 extends Struct {
@@ -1087,7 +1089,7 @@
   external double a2;
 
   String toString() =>
-      "(${a0}, ${[for (var i = 0; i < 2; i += 1) a1[i]]}, ${a2})";
+      "(${a0}, ${[for (var i0 = 0; i0 < 2; i0 += 1) a1[i0]]}, ${a2})";
 }
 
 class StructStruct32BytesHomogeneousDouble2 extends Struct {
@@ -1100,7 +1102,7 @@
   external double a2;
 
   String toString() =>
-      "(${a0}, ${[for (var i = 0; i < 2; i += 1) a1[i]]}, ${a2})";
+      "(${a0}, ${[for (var i0 = 0; i0 < 2; i0 += 1) a1[i0]]}, ${a2})";
 }
 
 class StructStruct16BytesMixed3 extends Struct {
@@ -1112,8 +1114,75 @@
   @Array(2)
   external Array<Int16> a2;
 
-  String toString() => "(${a0}, ${[for (var i = 0; i < 1; i += 1) a1[i]]}, ${[
-        for (var i = 0; i < 2; i += 1) a2[i]
+  String toString() => "(${a0}, ${[
+        for (var i0 = 0; i0 < 1; i0 += 1) a1[i0]
+      ]}, ${[for (var i0 = 0; i0 < 2; i0 += 1) a2[i0]]})";
+}
+
+class Struct8BytesInlineArrayMultiDimensionalInt extends Struct {
+  @Array(2, 2, 2)
+  external Array<Array<Array<Uint8>>> a0;
+
+  String toString() => "(${[
+        for (var i0 = 0; i0 < 2; i0 += 1)
+          [
+            for (var i1 = 0; i1 < 2; i1 += 1)
+              [for (var i2 = 0; i2 < 2; i2 += 1) a0[i0][i1][i2]]
+          ]
+      ]})";
+}
+
+class Struct32BytesInlineArrayMultiDimensionalInt extends Struct {
+  @Array(2, 2, 2, 2, 2)
+  external Array<Array<Array<Array<Array<Uint8>>>>> a0;
+
+  String toString() => "(${[
+        for (var i0 = 0; i0 < 2; i0 += 1)
+          [
+            for (var i1 = 0; i1 < 2; i1 += 1)
+              [
+                for (var i2 = 0; i2 < 2; i2 += 1)
+                  [
+                    for (var i3 = 0; i3 < 2; i3 += 1)
+                      [for (var i4 = 0; i4 < 2; i4 += 1) a0[i0][i1][i2][i3][i4]]
+                  ]
+              ]
+          ]
+      ]})";
+}
+
+class Struct64BytesInlineArrayMultiDimensionalInt extends Struct {
+  @Array.multi([2, 2, 2, 2, 2, 2])
+  external Array<Array<Array<Array<Array<Array<Uint8>>>>>> a0;
+
+  String toString() => "(${[
+        for (var i0 = 0; i0 < 2; i0 += 1)
+          [
+            for (var i1 = 0; i1 < 2; i1 += 1)
+              [
+                for (var i2 = 0; i2 < 2; i2 += 1)
+                  [
+                    for (var i3 = 0; i3 < 2; i3 += 1)
+                      [
+                        for (var i4 = 0; i4 < 2; i4 += 1)
+                          [
+                            for (var i5 = 0; i5 < 2; i5 += 1)
+                              a0[i0][i1][i2][i3][i4][i5]
+                          ]
+                      ]
+                  ]
+              ]
+          ]
+      ]})";
+}
+
+class Struct4BytesInlineArrayMultiDimensionalInt extends Struct {
+  @Array(2, 2)
+  external Array<Array<Struct1ByteInt>> a0;
+
+  String toString() => "(${[
+        for (var i0 = 0; i0 < 2; i0 += 1)
+          [for (var i1 = 0; i1 < 2; i1 += 1) a0[i0][i1]]
       ]})";
 }
 
@@ -5227,6 +5296,133 @@
   calloc.free(a9Pointer);
 }
 
+final passUint8Struct32BytesInlineArrayMultiDimensionalI =
+    ffiTestFunctions.lookupFunction<
+        Uint32 Function(
+            Uint8,
+            Struct32BytesInlineArrayMultiDimensionalInt,
+            Uint8,
+            Struct8BytesInlineArrayMultiDimensionalInt,
+            Uint8,
+            Struct8BytesInlineArrayMultiDimensionalInt,
+            Uint8),
+        int Function(
+            int,
+            Struct32BytesInlineArrayMultiDimensionalInt,
+            int,
+            Struct8BytesInlineArrayMultiDimensionalInt,
+            int,
+            Struct8BytesInlineArrayMultiDimensionalInt,
+            int)>("PassUint8Struct32BytesInlineArrayMultiDimensionalI");
+
+/// Test multi dimensional inline array struct as argument.
+void testPassUint8Struct32BytesInlineArrayMultiDimensionalI() {
+  int a0;
+  final a1Pointer = calloc<Struct32BytesInlineArrayMultiDimensionalInt>();
+  final Struct32BytesInlineArrayMultiDimensionalInt a1 = a1Pointer.ref;
+  int a2;
+  final a3Pointer = calloc<Struct8BytesInlineArrayMultiDimensionalInt>();
+  final Struct8BytesInlineArrayMultiDimensionalInt a3 = a3Pointer.ref;
+  int a4;
+  final a5Pointer = calloc<Struct8BytesInlineArrayMultiDimensionalInt>();
+  final Struct8BytesInlineArrayMultiDimensionalInt a5 = a5Pointer.ref;
+  int a6;
+
+  a0 = 1;
+  a1.a0[0][0][0][0][0] = 2;
+  a1.a0[0][0][0][0][1] = 3;
+  a1.a0[0][0][0][1][0] = 4;
+  a1.a0[0][0][0][1][1] = 5;
+  a1.a0[0][0][1][0][0] = 6;
+  a1.a0[0][0][1][0][1] = 7;
+  a1.a0[0][0][1][1][0] = 8;
+  a1.a0[0][0][1][1][1] = 9;
+  a1.a0[0][1][0][0][0] = 10;
+  a1.a0[0][1][0][0][1] = 11;
+  a1.a0[0][1][0][1][0] = 12;
+  a1.a0[0][1][0][1][1] = 13;
+  a1.a0[0][1][1][0][0] = 14;
+  a1.a0[0][1][1][0][1] = 15;
+  a1.a0[0][1][1][1][0] = 16;
+  a1.a0[0][1][1][1][1] = 17;
+  a1.a0[1][0][0][0][0] = 18;
+  a1.a0[1][0][0][0][1] = 19;
+  a1.a0[1][0][0][1][0] = 20;
+  a1.a0[1][0][0][1][1] = 21;
+  a1.a0[1][0][1][0][0] = 22;
+  a1.a0[1][0][1][0][1] = 23;
+  a1.a0[1][0][1][1][0] = 24;
+  a1.a0[1][0][1][1][1] = 25;
+  a1.a0[1][1][0][0][0] = 26;
+  a1.a0[1][1][0][0][1] = 27;
+  a1.a0[1][1][0][1][0] = 28;
+  a1.a0[1][1][0][1][1] = 29;
+  a1.a0[1][1][1][0][0] = 30;
+  a1.a0[1][1][1][0][1] = 31;
+  a1.a0[1][1][1][1][0] = 32;
+  a1.a0[1][1][1][1][1] = 33;
+  a2 = 34;
+  a3.a0[0][0][0] = 35;
+  a3.a0[0][0][1] = 36;
+  a3.a0[0][1][0] = 37;
+  a3.a0[0][1][1] = 38;
+  a3.a0[1][0][0] = 39;
+  a3.a0[1][0][1] = 40;
+  a3.a0[1][1][0] = 41;
+  a3.a0[1][1][1] = 42;
+  a4 = 43;
+  a5.a0[0][0][0] = 44;
+  a5.a0[0][0][1] = 45;
+  a5.a0[0][1][0] = 46;
+  a5.a0[0][1][1] = 47;
+  a5.a0[1][0][0] = 48;
+  a5.a0[1][0][1] = 49;
+  a5.a0[1][1][0] = 50;
+  a5.a0[1][1][1] = 51;
+  a6 = 52;
+
+  final result = passUint8Struct32BytesInlineArrayMultiDimensionalI(
+      a0, a1, a2, a3, a4, a5, a6);
+
+  print("result = $result");
+
+  Expect.equals(1378, result);
+
+  calloc.free(a1Pointer);
+  calloc.free(a3Pointer);
+  calloc.free(a5Pointer);
+}
+
+final passUint8Struct4BytesInlineArrayMultiDimensionalIn =
+    ffiTestFunctions.lookupFunction<
+        Uint32 Function(
+            Uint8, Struct4BytesInlineArrayMultiDimensionalInt, Uint8),
+        int Function(int, Struct4BytesInlineArrayMultiDimensionalInt,
+            int)>("PassUint8Struct4BytesInlineArrayMultiDimensionalIn");
+
+/// Test struct in multi dimensional inline array.
+void testPassUint8Struct4BytesInlineArrayMultiDimensionalIn() {
+  int a0;
+  final a1Pointer = calloc<Struct4BytesInlineArrayMultiDimensionalInt>();
+  final Struct4BytesInlineArrayMultiDimensionalInt a1 = a1Pointer.ref;
+  int a2;
+
+  a0 = 1;
+  a1.a0[0][0].a0 = 2;
+  a1.a0[0][1].a0 = -3;
+  a1.a0[1][0].a0 = 4;
+  a1.a0[1][1].a0 = -5;
+  a2 = 6;
+
+  final result = passUint8Struct4BytesInlineArrayMultiDimensionalIn(a0, a1, a2);
+
+  print("result = $result");
+
+  Expect.equals(5, result);
+
+  calloc.free(a1Pointer);
+}
+
 final returnStruct1ByteInt = ffiTestFunctions.lookupFunction<
     Struct1ByteInt Function(Int8),
     Struct1ByteInt Function(int)>("ReturnStruct1ByteInt");
diff --git a/tests/ffi/generator/c_types.dart b/tests/ffi/generator/c_types.dart
index 70fbfe1..e94b35b 100644
--- a/tests/ffi/generator/c_types.dart
+++ b/tests/ffi/generator/c_types.dart
@@ -133,8 +133,8 @@
   String get cStructField {
     String postFix = "";
     if (type is FixedLengthArrayType) {
-      final length = (type as FixedLengthArrayType).length;
-      postFix = "[$length]";
+      final dimensions = (type as FixedLengthArrayType).dimensions;
+      postFix = "[${dimensions.join("][")}]";
     }
     return "${type.cType} $name$postFix;";
   }
@@ -193,6 +193,12 @@
   bool get hasInlineArrays =>
       members.map((e) => e.type is FixedLengthArrayType).contains(true);
 
+  bool get hasMultiDimensionalInlineArrays => members
+      .map((e) => e.type)
+      .whereType<FixedLengthArrayType>()
+      .where((e) => e.isMulti)
+      .isNotEmpty;
+
   /// All members have the same type.
   bool get isHomogeneous => memberTypes.toSet().length == 1;
 
@@ -216,6 +222,9 @@
     }
     if (hasInlineArrays) {
       result += "InlineArray";
+      if (hasMultiDimensionalInlineArrays) {
+        result += "MultiDimensional";
+      }
     }
     if (members.length == 0) {
       // No suffix.
@@ -241,10 +250,37 @@
 
   FixedLengthArrayType(this.elementType, this.length);
 
+  factory FixedLengthArrayType.multi(CType elementType, List<int> dimensions) {
+    if (dimensions.length == 1) {
+      return FixedLengthArrayType(elementType, dimensions.single);
+    }
+
+    final remainingDimensions = dimensions.sublist(1);
+    final nestedArray =
+        FixedLengthArrayType.multi(elementType, remainingDimensions);
+    return FixedLengthArrayType(nestedArray, dimensions.first);
+  }
+
   String get cType => elementType.cType;
-  String get dartCType => "Array<${elementType.dartType}>";
+  String get dartCType => "Array<${elementType.dartCType}>";
   String get dartType => "Array<${elementType.dartCType}>";
-  String get dartStructFieldAnnotation => "@Array($length)";
+
+  String get dartStructFieldAnnotation {
+    if (dimensions.length > 5) {
+      return "@Array.multi([${dimensions.join(", ")}])";
+    }
+    return "@Array(${dimensions.join(", ")})";
+  }
+
+  List<int> get dimensions {
+    final elementType = this.elementType;
+    if (elementType is FixedLengthArrayType) {
+      return [length, ...elementType.dimensions];
+    }
+    return [length];
+  }
+
+  bool get isMulti => elementType is FixedLengthArrayType;
 
   bool get hasSize => elementType.hasSize;
   int get size => elementType.size * length;
diff --git a/tests/ffi/generator/structs_by_value_tests_configuration.dart b/tests/ffi/generator/structs_by_value_tests_configuration.dart
index c681f8a..ebc6a94 100644
--- a/tests/ffi/generator/structs_by_value_tests_configuration.dart
+++ b/tests/ffi/generator/structs_by_value_tests_configuration.dart
@@ -310,6 +310,24 @@
 On x64, it will exhaust the integer registers with the 6th argument.
 The rest goes on the stack.
 On arm, arguments are 4 byte aligned."""),
+  FunctionType(
+      [
+        uint8,
+        struct32bytesInlineArrayMultiDimesional,
+        uint8,
+        struct8bytesInlineArrayMultiDimesional,
+        uint8,
+        struct8bytesInlineArrayMultiDimesional,
+        uint8
+      ],
+      uint32,
+      """
+Test multi dimensional inline array struct as argument."""),
+  FunctionType(
+      [uint8, structMultiDimensionalStruct, uint8],
+      uint32,
+      """
+Test struct in multi dimensional inline array."""),
   FunctionType(struct1byteInt.memberTypes, struct1byteInt, """
 Smallest struct with data."""),
   FunctionType(struct3bytesInt.memberTypes, struct3bytesInt, """
@@ -507,6 +525,10 @@
   struct16bytesFloatInlineNested,
   struct32bytesDoubleInlineNested,
   struct16bytesMixedInlineNested,
+  struct8bytesInlineArrayMultiDimesional,
+  struct32bytesInlineArrayMultiDimesional,
+  struct64bytesInlineArrayMultiDimesional,
+  structMultiDimensionalStruct,
 ];
 
 final struct1byteInt = StructType([int8]);
@@ -634,3 +656,19 @@
   FixedLengthArrayType(StructType([float, int16, int16]), 1),
   FixedLengthArrayType(int16, 2),
 ], "Struct16BytesMixed3");
+
+final struct8bytesInlineArrayMultiDimesional = StructType([
+  FixedLengthArrayType.multi(uint8, [2, 2, 2])
+]);
+
+final struct32bytesInlineArrayMultiDimesional = StructType([
+  FixedLengthArrayType.multi(uint8, [2, 2, 2, 2, 2])
+]);
+
+final struct64bytesInlineArrayMultiDimesional = StructType([
+  FixedLengthArrayType.multi(uint8, [2, 2, 2, 2, 2, 2])
+]);
+
+final structMultiDimensionalStruct = StructType([
+  FixedLengthArrayType.multi(struct1byteInt, [2, 2])
+]);
diff --git a/tests/ffi/generator/structs_by_value_tests_generator.dart b/tests/ffi/generator/structs_by_value_tests_generator.dart
index 7ebb535..51f51ab 100644
--- a/tests/ffi/generator/structs_by_value_tests_generator.dart
+++ b/tests/ffi/generator/structs_by_value_tests_generator.dart
@@ -332,7 +332,7 @@
       case FixedLengthArrayType:
         final this_ = this as FixedLengthArrayType;
         return """
-for(int i = 0; i < ${this_.length}; i++){
+for (int i = 0; i < ${this_.length}; i++){
   ${this_.elementType.dartExpectsStatements("$expected[i]", "$actual[i]")}
 }
 """;
@@ -370,7 +370,7 @@
       case FixedLengthArrayType:
         final this_ = this as FixedLengthArrayType;
         return """
-for(intptr_t i = 0; i < ${this_.length}; i++){
+for (intptr_t i = 0; i < ${this_.length}; i++){
   ${this_.elementType.cExpectsStatements("$expected[i]", "$actual[i]")}
 }
 """;
@@ -397,7 +397,7 @@
       case FixedLengthArrayType:
         final this_ = this as FixedLengthArrayType;
         return """
-for(intptr_t i = 0; i < ${this_.length}; i++){
+for (intptr_t i = 0; i < ${this_.length}; i++){
   ${this_.elementType.cExpectsZeroStatements("$actual[i]")}
 }
 """;
@@ -461,8 +461,18 @@
     }
     String toStringBody = members.map((m) {
       if (m.type is FixedLengthArrayType) {
-        final length = (m.type as FixedLengthArrayType).length;
-        return "\$\{[for (var i = 0; i < $length; i += 1) ${m.name}[i]]\}";
+        int dimensionNumber = 0;
+        String inlineFor = "";
+        String read = m.name;
+        String closing = "";
+        for (final dimension in (m.type as FixedLengthArrayType).dimensions) {
+          final i = "i$dimensionNumber";
+          inlineFor += "[for (var $i = 0; $i < $dimension; $i += 1)";
+          read += "[$i]";
+          closing += "]";
+          dimensionNumber++;
+        }
+        return "\$\{$inlineFor $read $closing\}";
       }
       return "\$\{${m.name}\}";
     }).join(", ");
diff --git a/tests/ffi/inline_array_multi_dimensional_test.dart b/tests/ffi/inline_array_multi_dimensional_test.dart
new file mode 100644
index 0000000..15d2059
--- /dev/null
+++ b/tests/ffi/inline_array_multi_dimensional_test.dart
@@ -0,0 +1,164 @@
+// 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.
+//
+// SharedObjects=ffi_test_functions
+
+import 'dart:ffi';
+
+import "package:expect/expect.dart";
+import 'package:ffi/ffi.dart';
+
+// Reuse struct definitions.
+import 'function_structs_by_value_generated_test.dart';
+
+void main() {
+  testSizeOf();
+  testLoad();
+  testLoadMultiAnnotation();
+  testStore();
+  testToString();
+  testRange();
+}
+
+void testSizeOf() {
+  Expect.equals(32, sizeOf<Struct32BytesInlineArrayMultiDimensionalInt>());
+  Expect.equals(64, sizeOf<Struct64BytesInlineArrayMultiDimensionalInt>());
+}
+
+/// Tests the load of nested `Array`s.
+///
+/// Only stores into arrays which do not have nested arrays.
+void testLoad() {
+  final Pointer<Struct32BytesInlineArrayMultiDimensionalInt> pointer = calloc();
+  final struct = pointer.ref;
+  final array = struct.a0;
+  for (int i = 0; i < 2; i++) {
+    for (int j = 0; j < 2; j++) {
+      for (int k = 0; k < 2; k++) {
+        for (int l = 0; l < 2; l++) {
+          for (int m = 0; m < 2; m++) {
+            array[i][j][k][l][m] = i + j + k + l + m;
+          }
+        }
+      }
+    }
+  }
+  for (int i = 0; i < 2; i++) {
+    for (int j = 0; j < 2; j++) {
+      for (int k = 0; k < 2; k++) {
+        for (int l = 0; l < 2; l++) {
+          for (int m = 0; m < 2; m++) {
+            Expect.equals(i + j + k + l + m, array[i][j][k][l][m]);
+          }
+        }
+      }
+    }
+  }
+  calloc.free(pointer);
+}
+
+/// Tests the load of nested `Array`s.
+///
+/// Only stores into arrays which do not have nested arrays.
+void testLoadMultiAnnotation() {
+  final Pointer<Struct64BytesInlineArrayMultiDimensionalInt> pointer = calloc();
+  final struct = pointer.ref;
+  final array = struct.a0;
+  for (int i = 0; i < 2; i++) {
+    for (int j = 0; j < 2; j++) {
+      for (int k = 0; k < 2; k++) {
+        for (int l = 0; l < 2; l++) {
+          for (int m = 0; m < 2; m++) {
+            for (int o = 0; o < 2; o++) {
+              array[i][j][k][l][m][o] = i + j + k + l + m + o;
+            }
+          }
+        }
+      }
+    }
+  }
+  for (int i = 0; i < 2; i++) {
+    for (int j = 0; j < 2; j++) {
+      for (int k = 0; k < 2; k++) {
+        for (int l = 0; l < 2; l++) {
+          for (int m = 0; m < 2; m++) {
+            for (int o = 0; o < 2; o++) {
+              Expect.equals(i + j + k + l + m + o, array[i][j][k][l][m][o]);
+            }
+          }
+        }
+      }
+    }
+  }
+  calloc.free(pointer);
+}
+
+void testStore() {
+  final Pointer<Struct32BytesInlineArrayMultiDimensionalInt> pointer = calloc();
+  final struct = pointer.ref;
+  final array = struct.a0;
+  for (int i = 0; i < 2; i++) {
+    for (int j = 0; j < 2; j++) {
+      for (int k = 0; k < 2; k++) {
+        for (int l = 0; l < 2; l++) {
+          for (int m = 0; m < 2; m++) {
+            array[i][j][k][l][m] = i + j + k + l + m;
+          }
+        }
+      }
+    }
+  }
+  array[0] = array[1]; // Copy many things.
+  for (int j = 0; j < 2; j++) {
+    for (int k = 0; k < 2; k++) {
+      for (int l = 0; l < 2; l++) {
+        for (int m = 0; m < 2; m++) {
+          Expect.equals(array[1][j][k][l][m], array[0][j][k][l][m]);
+        }
+      }
+    }
+  }
+  calloc.free(pointer);
+}
+
+// // Tests the toString of the test generator.
+void testToString() {
+  final Pointer<Struct32BytesInlineArrayMultiDimensionalInt> pointer = calloc();
+  final struct = pointer.ref;
+  final array = struct.a0;
+  for (int i = 0; i < 2; i++) {
+    for (int j = 0; j < 2; j++) {
+      for (int k = 0; k < 2; k++) {
+        for (int l = 0; l < 2; l++) {
+          for (int m = 0; m < 2; m++) {
+            array[i][j][k][l][m] = 16 * i + 8 * j + 4 * k + 2 * l + m;
+          }
+        }
+      }
+    }
+  }
+  Expect.equals(
+      "([[[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], [[[8, 9], [10, 11]], [[12, 13], [14, 15]]]], [[[[16, 17], [18, 19]], [[20, 21], [22, 23]]], [[[24, 25], [26, 27]], [[28, 29], [30, 31]]]]])",
+      struct.toString());
+  calloc.free(pointer);
+}
+
+void testRange() {
+  final pointer = calloc<Struct32BytesInlineArrayMultiDimensionalInt>();
+  final struct = pointer.ref;
+  final array = struct.a0;
+  array[0];
+  array[1];
+  Expect.throws(() => array[-1]);
+  Expect.throws(() => array[-1] = array[1]);
+  Expect.throws(() => array[2]);
+  Expect.throws(() => array[2] = array[1]);
+  array[0][0];
+  array[0][1];
+  Expect.throws(() => array[0][-1]);
+  Expect.throws(() => array[0][-1] = array[0][1]);
+  Expect.throws(() => array[0][2]);
+  Expect.throws(() => array[0][2] = array[0][1]);
+  calloc.free(pointer);
+}
diff --git a/tests/ffi/inline_array_test.dart b/tests/ffi/inline_array_test.dart
index 518d62e..868d117 100644
--- a/tests/ffi/inline_array_test.dart
+++ b/tests/ffi/inline_array_test.dart
@@ -30,11 +30,11 @@
 void testLoad() {
   final pointer = calloc<Struct8BytesInlineArrayInt>();
   final struct = pointer.ref;
-  final cArray = struct.a0;
+  final array = struct.a0;
   pointer.cast<Uint8>()[0] = 42;
   pointer.cast<Uint8>()[7] = 3;
-  Expect.equals(42, cArray[0]);
-  Expect.equals(3, cArray[7]);
+  Expect.equals(42, array[0]);
+  Expect.equals(3, array[7]);
   calloc.free(pointer);
 }
 
@@ -55,9 +55,9 @@
 void testToString() {
   final pointer = calloc<Struct8BytesInlineArrayInt>();
   final struct = pointer.ref;
-  final cArray = struct.a0;
+  final array = struct.a0;
   for (var i = 0; i < 8; i++) {
-    cArray[i] = i;
+    array[i] = i;
   }
   Expect.equals("([0, 1, 2, 3, 4, 5, 6, 7])", struct.toString());
   calloc.free(pointer);
@@ -77,14 +77,14 @@
 void testRange() {
   final pointer = calloc<Struct8BytesInlineArrayInt>();
   final struct = pointer.ref;
-  final cArray = struct.a0;
-  cArray[0] = 1;
-  Expect.equals(1, cArray[0]);
-  cArray[7] = 7;
-  Expect.equals(7, cArray[7]);
-  Expect.throws(() => cArray[-1]);
-  Expect.throws(() => cArray[-1] = 0);
-  Expect.throws(() => cArray[8]);
-  Expect.throws(() => cArray[8] = 0);
+  final array = struct.a0;
+  array[0] = 1;
+  Expect.equals(1, array[0]);
+  array[7] = 7;
+  Expect.equals(7, array[7]);
+  Expect.throws(() => array[-1]);
+  Expect.throws(() => array[-1] = 0);
+  Expect.throws(() => array[8]);
+  Expect.throws(() => array[8] = 0);
   calloc.free(pointer);
 }
diff --git a/tests/ffi/vmspecific_static_checks_test.dart b/tests/ffi/vmspecific_static_checks_test.dart
index 3ec5ac0..746b238 100644
--- a/tests/ffi/vmspecific_static_checks_test.dart
+++ b/tests/ffi/vmspecific_static_checks_test.dart
@@ -677,8 +677,29 @@
 }
 
 class TestStruct1402 extends Struct {
-  @Array(8) //# 1402: compile-time error
+  @Array(8, 8, 8) //# 1402: compile-time error
   external Array<Array<Uint8>> a0; //# 1402: compile-time error
 
   external Pointer<Uint8> notEmpty;
 }
+
+class TestStruct1403 extends Struct {
+  @Array(8, 8) //# 1403: compile-time error
+  external Array<Array<Array<Uint8>>> a0; //# 1403: compile-time error
+
+  external Pointer<Uint8> notEmpty;
+}
+
+class TestStruct1404 extends Struct {
+  @Array.multi([8, 8, 8]) //# 1404: compile-time error
+  external Array<Array<Uint8>> a0; //# 1404: compile-time error
+
+  external Pointer<Uint8> notEmpty;
+}
+
+class TestStruct1405 extends Struct {
+  @Array.multi([8, 8]) //# 1405: compile-time error
+  external Array<Array<Array<Uint8>>> a0; //# 1405: compile-time error
+
+  external Pointer<Uint8> notEmpty;
+}
diff --git a/tests/ffi_2/function_callbacks_structs_by_value_generated_test.dart b/tests/ffi_2/function_callbacks_structs_by_value_generated_test.dart
index ec212c5..d4650fe 100644
--- a/tests/ffi_2/function_callbacks_structs_by_value_generated_test.dart
+++ b/tests/ffi_2/function_callbacks_structs_by_value_generated_test.dart
@@ -273,6 +273,18 @@
           passStructStruct16BytesMixed3x10, 0.0),
       passStructStruct16BytesMixed3x10AfterCallback),
   CallbackTest.withCheck(
+      "PassUint8Struct32BytesInlineArrayMultiDimensionalI",
+      Pointer.fromFunction<
+              PassUint8Struct32BytesInlineArrayMultiDimensionalIType>(
+          passUint8Struct32BytesInlineArrayMultiDimensionalI, 0),
+      passUint8Struct32BytesInlineArrayMultiDimensionalIAfterCallback),
+  CallbackTest.withCheck(
+      "PassUint8Struct4BytesInlineArrayMultiDimensionalIn",
+      Pointer.fromFunction<
+              PassUint8Struct4BytesInlineArrayMultiDimensionalInType>(
+          passUint8Struct4BytesInlineArrayMultiDimensionalIn, 0),
+      passUint8Struct4BytesInlineArrayMultiDimensionalInAfterCallback),
+  CallbackTest.withCheck(
       "ReturnStruct1ByteInt",
       Pointer.fromFunction<ReturnStruct1ByteIntType>(returnStruct1ByteInt),
       returnStruct1ByteIntAfterCallback),
@@ -6159,6 +6171,245 @@
   Expect.approxEquals(30.0, result);
 }
 
+typedef PassUint8Struct32BytesInlineArrayMultiDimensionalIType
+    = Uint32 Function(
+        Uint8,
+        Struct32BytesInlineArrayMultiDimensionalInt,
+        Uint8,
+        Struct8BytesInlineArrayMultiDimensionalInt,
+        Uint8,
+        Struct8BytesInlineArrayMultiDimensionalInt,
+        Uint8);
+
+// Global variables to be able to test inputs after callback returned.
+int passUint8Struct32BytesInlineArrayMultiDimensionalI_a0 = 0;
+Struct32BytesInlineArrayMultiDimensionalInt
+    passUint8Struct32BytesInlineArrayMultiDimensionalI_a1 =
+    Struct32BytesInlineArrayMultiDimensionalInt();
+int passUint8Struct32BytesInlineArrayMultiDimensionalI_a2 = 0;
+Struct8BytesInlineArrayMultiDimensionalInt
+    passUint8Struct32BytesInlineArrayMultiDimensionalI_a3 =
+    Struct8BytesInlineArrayMultiDimensionalInt();
+int passUint8Struct32BytesInlineArrayMultiDimensionalI_a4 = 0;
+Struct8BytesInlineArrayMultiDimensionalInt
+    passUint8Struct32BytesInlineArrayMultiDimensionalI_a5 =
+    Struct8BytesInlineArrayMultiDimensionalInt();
+int passUint8Struct32BytesInlineArrayMultiDimensionalI_a6 = 0;
+
+// Result variable also global, so we can delete it after the callback.
+int passUint8Struct32BytesInlineArrayMultiDimensionalIResult = 0;
+
+int passUint8Struct32BytesInlineArrayMultiDimensionalICalculateResult() {
+  int result = 0;
+
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a0;
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][0][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][0][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][0][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][0][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][1][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][1][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][1][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][0][1][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][0][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][0][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][0][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][0][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][1][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][1][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][1][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[0][1][1][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][0][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][0][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][0][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][0][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][1][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][1][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][1][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][0][1][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][0][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][0][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][0][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][0][1][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][1][0][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][1][0][1];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][1][1][0];
+  result +=
+      passUint8Struct32BytesInlineArrayMultiDimensionalI_a1.a0[1][1][1][1][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a2;
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[0][0][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[0][0][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[0][1][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[0][1][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[1][0][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[1][0][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[1][1][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a3.a0[1][1][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a4;
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[0][0][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[0][0][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[0][1][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[0][1][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[1][0][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[1][0][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[1][1][0];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a5.a0[1][1][1];
+  result += passUint8Struct32BytesInlineArrayMultiDimensionalI_a6;
+
+  passUint8Struct32BytesInlineArrayMultiDimensionalIResult = result;
+
+  return result;
+}
+
+/// Test multi dimensional inline array struct as argument.
+int passUint8Struct32BytesInlineArrayMultiDimensionalI(
+    int a0,
+    Struct32BytesInlineArrayMultiDimensionalInt a1,
+    int a2,
+    Struct8BytesInlineArrayMultiDimensionalInt a3,
+    int a4,
+    Struct8BytesInlineArrayMultiDimensionalInt a5,
+    int a6) {
+  print(
+      "passUint8Struct32BytesInlineArrayMultiDimensionalI(${a0}, ${a1}, ${a2}, ${a3}, ${a4}, ${a5}, ${a6})");
+
+  // In legacy mode, possibly return null.
+  if (a0 == 84) {
+    print("returning null!");
+    return null;
+  }
+
+  // In both nnbd and legacy mode, possibly throw.
+  if (a0 == 42 || a0 == 84) {
+    print("throwing!");
+    throw Exception(
+        "PassUint8Struct32BytesInlineArrayMultiDimensionalI throwing on purpose!");
+  }
+
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a0 = a0;
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a1 = a1;
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a2 = a2;
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a3 = a3;
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a4 = a4;
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a5 = a5;
+  passUint8Struct32BytesInlineArrayMultiDimensionalI_a6 = a6;
+
+  final result =
+      passUint8Struct32BytesInlineArrayMultiDimensionalICalculateResult();
+
+  print("result = $result");
+
+  return result;
+}
+
+void passUint8Struct32BytesInlineArrayMultiDimensionalIAfterCallback() {
+  final result =
+      passUint8Struct32BytesInlineArrayMultiDimensionalICalculateResult();
+
+  print("after callback result = $result");
+
+  Expect.equals(1378, result);
+}
+
+typedef PassUint8Struct4BytesInlineArrayMultiDimensionalInType = Uint32
+    Function(Uint8, Struct4BytesInlineArrayMultiDimensionalInt, Uint8);
+
+// Global variables to be able to test inputs after callback returned.
+int passUint8Struct4BytesInlineArrayMultiDimensionalIn_a0 = 0;
+Struct4BytesInlineArrayMultiDimensionalInt
+    passUint8Struct4BytesInlineArrayMultiDimensionalIn_a1 =
+    Struct4BytesInlineArrayMultiDimensionalInt();
+int passUint8Struct4BytesInlineArrayMultiDimensionalIn_a2 = 0;
+
+// Result variable also global, so we can delete it after the callback.
+int passUint8Struct4BytesInlineArrayMultiDimensionalInResult = 0;
+
+int passUint8Struct4BytesInlineArrayMultiDimensionalInCalculateResult() {
+  int result = 0;
+
+  result += passUint8Struct4BytesInlineArrayMultiDimensionalIn_a0;
+  result += passUint8Struct4BytesInlineArrayMultiDimensionalIn_a1.a0[0][0].a0;
+  result += passUint8Struct4BytesInlineArrayMultiDimensionalIn_a1.a0[0][1].a0;
+  result += passUint8Struct4BytesInlineArrayMultiDimensionalIn_a1.a0[1][0].a0;
+  result += passUint8Struct4BytesInlineArrayMultiDimensionalIn_a1.a0[1][1].a0;
+  result += passUint8Struct4BytesInlineArrayMultiDimensionalIn_a2;
+
+  passUint8Struct4BytesInlineArrayMultiDimensionalInResult = result;
+
+  return result;
+}
+
+/// Test struct in multi dimensional inline array.
+int passUint8Struct4BytesInlineArrayMultiDimensionalIn(
+    int a0, Struct4BytesInlineArrayMultiDimensionalInt a1, int a2) {
+  print(
+      "passUint8Struct4BytesInlineArrayMultiDimensionalIn(${a0}, ${a1}, ${a2})");
+
+  // In legacy mode, possibly return null.
+  if (a0 == 84) {
+    print("returning null!");
+    return null;
+  }
+
+  // In both nnbd and legacy mode, possibly throw.
+  if (a0 == 42 || a0 == 84) {
+    print("throwing!");
+    throw Exception(
+        "PassUint8Struct4BytesInlineArrayMultiDimensionalIn throwing on purpose!");
+  }
+
+  passUint8Struct4BytesInlineArrayMultiDimensionalIn_a0 = a0;
+  passUint8Struct4BytesInlineArrayMultiDimensionalIn_a1 = a1;
+  passUint8Struct4BytesInlineArrayMultiDimensionalIn_a2 = a2;
+
+  final result =
+      passUint8Struct4BytesInlineArrayMultiDimensionalInCalculateResult();
+
+  print("result = $result");
+
+  return result;
+}
+
+void passUint8Struct4BytesInlineArrayMultiDimensionalInAfterCallback() {
+  final result =
+      passUint8Struct4BytesInlineArrayMultiDimensionalInCalculateResult();
+
+  print("after callback result = $result");
+
+  Expect.equals(5, result);
+}
+
 typedef ReturnStruct1ByteIntType = Struct1ByteInt Function(Int8);
 
 // Global variables to be able to test inputs after callback returned.
diff --git a/tests/ffi_2/function_structs_by_value_generated_test.dart b/tests/ffi_2/function_structs_by_value_generated_test.dart
index a3f7561..91747de 100644
--- a/tests/ffi_2/function_structs_by_value_generated_test.dart
+++ b/tests/ffi_2/function_structs_by_value_generated_test.dart
@@ -68,6 +68,8 @@
     testPassStructStruct16BytesHomogeneousFloat2x5();
     testPassStructStruct32BytesHomogeneousDouble2x5();
     testPassStructStruct16BytesMixed3x10();
+    testPassUint8Struct32BytesInlineArrayMultiDimensionalI();
+    testPassUint8Struct4BytesInlineArrayMultiDimensionalIn();
     testReturnStruct1ByteInt();
     testReturnStruct3BytesHomogeneousUint8();
     testReturnStruct3BytesInt2ByteAligned();
@@ -1043,7 +1045,7 @@
   @Array(8)
   Array<Uint8> a0;
 
-  String toString() => "(${[for (var i = 0; i < 8; i += 1) a0[i]]})";
+  String toString() => "(${[for (var i0 = 0; i0 < 8; i0 += 1) a0[i0]]})";
 }
 
 class StructInlineArrayIrregular extends Struct {
@@ -1053,14 +1055,14 @@
   @Uint8()
   int a1;
 
-  String toString() => "(${[for (var i = 0; i < 2; i += 1) a0[i]]}, ${a1})";
+  String toString() => "(${[for (var i0 = 0; i0 < 2; i0 += 1) a0[i0]]}, ${a1})";
 }
 
 class StructInlineArray100Bytes extends Struct {
   @Array(100)
   Array<Uint8> a0;
 
-  String toString() => "(${[for (var i = 0; i < 100; i += 1) a0[i]]})";
+  String toString() => "(${[for (var i0 = 0; i0 < 100; i0 += 1) a0[i0]]})";
 }
 
 class StructInlineArrayBig extends Struct {
@@ -1074,7 +1076,7 @@
   Array<Uint8> a2;
 
   String toString() =>
-      "(${a0}, ${a1}, ${[for (var i = 0; i < 4000; i += 1) a2[i]]})";
+      "(${a0}, ${a1}, ${[for (var i0 = 0; i0 < 4000; i0 += 1) a2[i0]]})";
 }
 
 class StructStruct16BytesHomogeneousFloat2 extends Struct {
@@ -1087,7 +1089,7 @@
   double a2;
 
   String toString() =>
-      "(${a0}, ${[for (var i = 0; i < 2; i += 1) a1[i]]}, ${a2})";
+      "(${a0}, ${[for (var i0 = 0; i0 < 2; i0 += 1) a1[i0]]}, ${a2})";
 }
 
 class StructStruct32BytesHomogeneousDouble2 extends Struct {
@@ -1100,7 +1102,7 @@
   double a2;
 
   String toString() =>
-      "(${a0}, ${[for (var i = 0; i < 2; i += 1) a1[i]]}, ${a2})";
+      "(${a0}, ${[for (var i0 = 0; i0 < 2; i0 += 1) a1[i0]]}, ${a2})";
 }
 
 class StructStruct16BytesMixed3 extends Struct {
@@ -1112,8 +1114,75 @@
   @Array(2)
   Array<Int16> a2;
 
-  String toString() => "(${a0}, ${[for (var i = 0; i < 1; i += 1) a1[i]]}, ${[
-        for (var i = 0; i < 2; i += 1) a2[i]
+  String toString() => "(${a0}, ${[
+        for (var i0 = 0; i0 < 1; i0 += 1) a1[i0]
+      ]}, ${[for (var i0 = 0; i0 < 2; i0 += 1) a2[i0]]})";
+}
+
+class Struct8BytesInlineArrayMultiDimensionalInt extends Struct {
+  @Array(2, 2, 2)
+  Array<Array<Array<Uint8>>> a0;
+
+  String toString() => "(${[
+        for (var i0 = 0; i0 < 2; i0 += 1)
+          [
+            for (var i1 = 0; i1 < 2; i1 += 1)
+              [for (var i2 = 0; i2 < 2; i2 += 1) a0[i0][i1][i2]]
+          ]
+      ]})";
+}
+
+class Struct32BytesInlineArrayMultiDimensionalInt extends Struct {
+  @Array(2, 2, 2, 2, 2)
+  Array<Array<Array<Array<Array<Uint8>>>>> a0;
+
+  String toString() => "(${[
+        for (var i0 = 0; i0 < 2; i0 += 1)
+          [
+            for (var i1 = 0; i1 < 2; i1 += 1)
+              [
+                for (var i2 = 0; i2 < 2; i2 += 1)
+                  [
+                    for (var i3 = 0; i3 < 2; i3 += 1)
+                      [for (var i4 = 0; i4 < 2; i4 += 1) a0[i0][i1][i2][i3][i4]]
+                  ]
+              ]
+          ]
+      ]})";
+}
+
+class Struct64BytesInlineArrayMultiDimensionalInt extends Struct {
+  @Array.multi([2, 2, 2, 2, 2, 2])
+  Array<Array<Array<Array<Array<Array<Uint8>>>>>> a0;
+
+  String toString() => "(${[
+        for (var i0 = 0; i0 < 2; i0 += 1)
+          [
+            for (var i1 = 0; i1 < 2; i1 += 1)
+              [
+                for (var i2 = 0; i2 < 2; i2 += 1)
+                  [
+                    for (var i3 = 0; i3 < 2; i3 += 1)
+                      [
+                        for (var i4 = 0; i4 < 2; i4 += 1)
+                          [
+                            for (var i5 = 0; i5 < 2; i5 += 1)
+                              a0[i0][i1][i2][i3][i4][i5]
+                          ]
+                      ]
+                  ]
+              ]
+          ]
+      ]})";
+}
+
+class Struct4BytesInlineArrayMultiDimensionalInt extends Struct {
+  @Array(2, 2)
+  Array<Array<Struct1ByteInt>> a0;
+
+  String toString() => "(${[
+        for (var i0 = 0; i0 < 2; i0 += 1)
+          [for (var i1 = 0; i1 < 2; i1 += 1) a0[i0][i1]]
       ]})";
 }
 
@@ -5227,6 +5296,133 @@
   calloc.free(a9Pointer);
 }
 
+final passUint8Struct32BytesInlineArrayMultiDimensionalI =
+    ffiTestFunctions.lookupFunction<
+        Uint32 Function(
+            Uint8,
+            Struct32BytesInlineArrayMultiDimensionalInt,
+            Uint8,
+            Struct8BytesInlineArrayMultiDimensionalInt,
+            Uint8,
+            Struct8BytesInlineArrayMultiDimensionalInt,
+            Uint8),
+        int Function(
+            int,
+            Struct32BytesInlineArrayMultiDimensionalInt,
+            int,
+            Struct8BytesInlineArrayMultiDimensionalInt,
+            int,
+            Struct8BytesInlineArrayMultiDimensionalInt,
+            int)>("PassUint8Struct32BytesInlineArrayMultiDimensionalI");
+
+/// Test multi dimensional inline array struct as argument.
+void testPassUint8Struct32BytesInlineArrayMultiDimensionalI() {
+  int a0;
+  final a1Pointer = calloc<Struct32BytesInlineArrayMultiDimensionalInt>();
+  final Struct32BytesInlineArrayMultiDimensionalInt a1 = a1Pointer.ref;
+  int a2;
+  final a3Pointer = calloc<Struct8BytesInlineArrayMultiDimensionalInt>();
+  final Struct8BytesInlineArrayMultiDimensionalInt a3 = a3Pointer.ref;
+  int a4;
+  final a5Pointer = calloc<Struct8BytesInlineArrayMultiDimensionalInt>();
+  final Struct8BytesInlineArrayMultiDimensionalInt a5 = a5Pointer.ref;
+  int a6;
+
+  a0 = 1;
+  a1.a0[0][0][0][0][0] = 2;
+  a1.a0[0][0][0][0][1] = 3;
+  a1.a0[0][0][0][1][0] = 4;
+  a1.a0[0][0][0][1][1] = 5;
+  a1.a0[0][0][1][0][0] = 6;
+  a1.a0[0][0][1][0][1] = 7;
+  a1.a0[0][0][1][1][0] = 8;
+  a1.a0[0][0][1][1][1] = 9;
+  a1.a0[0][1][0][0][0] = 10;
+  a1.a0[0][1][0][0][1] = 11;
+  a1.a0[0][1][0][1][0] = 12;
+  a1.a0[0][1][0][1][1] = 13;
+  a1.a0[0][1][1][0][0] = 14;
+  a1.a0[0][1][1][0][1] = 15;
+  a1.a0[0][1][1][1][0] = 16;
+  a1.a0[0][1][1][1][1] = 17;
+  a1.a0[1][0][0][0][0] = 18;
+  a1.a0[1][0][0][0][1] = 19;
+  a1.a0[1][0][0][1][0] = 20;
+  a1.a0[1][0][0][1][1] = 21;
+  a1.a0[1][0][1][0][0] = 22;
+  a1.a0[1][0][1][0][1] = 23;
+  a1.a0[1][0][1][1][0] = 24;
+  a1.a0[1][0][1][1][1] = 25;
+  a1.a0[1][1][0][0][0] = 26;
+  a1.a0[1][1][0][0][1] = 27;
+  a1.a0[1][1][0][1][0] = 28;
+  a1.a0[1][1][0][1][1] = 29;
+  a1.a0[1][1][1][0][0] = 30;
+  a1.a0[1][1][1][0][1] = 31;
+  a1.a0[1][1][1][1][0] = 32;
+  a1.a0[1][1][1][1][1] = 33;
+  a2 = 34;
+  a3.a0[0][0][0] = 35;
+  a3.a0[0][0][1] = 36;
+  a3.a0[0][1][0] = 37;
+  a3.a0[0][1][1] = 38;
+  a3.a0[1][0][0] = 39;
+  a3.a0[1][0][1] = 40;
+  a3.a0[1][1][0] = 41;
+  a3.a0[1][1][1] = 42;
+  a4 = 43;
+  a5.a0[0][0][0] = 44;
+  a5.a0[0][0][1] = 45;
+  a5.a0[0][1][0] = 46;
+  a5.a0[0][1][1] = 47;
+  a5.a0[1][0][0] = 48;
+  a5.a0[1][0][1] = 49;
+  a5.a0[1][1][0] = 50;
+  a5.a0[1][1][1] = 51;
+  a6 = 52;
+
+  final result = passUint8Struct32BytesInlineArrayMultiDimensionalI(
+      a0, a1, a2, a3, a4, a5, a6);
+
+  print("result = $result");
+
+  Expect.equals(1378, result);
+
+  calloc.free(a1Pointer);
+  calloc.free(a3Pointer);
+  calloc.free(a5Pointer);
+}
+
+final passUint8Struct4BytesInlineArrayMultiDimensionalIn =
+    ffiTestFunctions.lookupFunction<
+        Uint32 Function(
+            Uint8, Struct4BytesInlineArrayMultiDimensionalInt, Uint8),
+        int Function(int, Struct4BytesInlineArrayMultiDimensionalInt,
+            int)>("PassUint8Struct4BytesInlineArrayMultiDimensionalIn");
+
+/// Test struct in multi dimensional inline array.
+void testPassUint8Struct4BytesInlineArrayMultiDimensionalIn() {
+  int a0;
+  final a1Pointer = calloc<Struct4BytesInlineArrayMultiDimensionalInt>();
+  final Struct4BytesInlineArrayMultiDimensionalInt a1 = a1Pointer.ref;
+  int a2;
+
+  a0 = 1;
+  a1.a0[0][0].a0 = 2;
+  a1.a0[0][1].a0 = -3;
+  a1.a0[1][0].a0 = 4;
+  a1.a0[1][1].a0 = -5;
+  a2 = 6;
+
+  final result = passUint8Struct4BytesInlineArrayMultiDimensionalIn(a0, a1, a2);
+
+  print("result = $result");
+
+  Expect.equals(5, result);
+
+  calloc.free(a1Pointer);
+}
+
 final returnStruct1ByteInt = ffiTestFunctions.lookupFunction<
     Struct1ByteInt Function(Int8),
     Struct1ByteInt Function(int)>("ReturnStruct1ByteInt");
diff --git a/tests/ffi_2/generator/c_types.dart b/tests/ffi_2/generator/c_types.dart
index 70fbfe1..e94b35b 100644
--- a/tests/ffi_2/generator/c_types.dart
+++ b/tests/ffi_2/generator/c_types.dart
@@ -133,8 +133,8 @@
   String get cStructField {
     String postFix = "";
     if (type is FixedLengthArrayType) {
-      final length = (type as FixedLengthArrayType).length;
-      postFix = "[$length]";
+      final dimensions = (type as FixedLengthArrayType).dimensions;
+      postFix = "[${dimensions.join("][")}]";
     }
     return "${type.cType} $name$postFix;";
   }
@@ -193,6 +193,12 @@
   bool get hasInlineArrays =>
       members.map((e) => e.type is FixedLengthArrayType).contains(true);
 
+  bool get hasMultiDimensionalInlineArrays => members
+      .map((e) => e.type)
+      .whereType<FixedLengthArrayType>()
+      .where((e) => e.isMulti)
+      .isNotEmpty;
+
   /// All members have the same type.
   bool get isHomogeneous => memberTypes.toSet().length == 1;
 
@@ -216,6 +222,9 @@
     }
     if (hasInlineArrays) {
       result += "InlineArray";
+      if (hasMultiDimensionalInlineArrays) {
+        result += "MultiDimensional";
+      }
     }
     if (members.length == 0) {
       // No suffix.
@@ -241,10 +250,37 @@
 
   FixedLengthArrayType(this.elementType, this.length);
 
+  factory FixedLengthArrayType.multi(CType elementType, List<int> dimensions) {
+    if (dimensions.length == 1) {
+      return FixedLengthArrayType(elementType, dimensions.single);
+    }
+
+    final remainingDimensions = dimensions.sublist(1);
+    final nestedArray =
+        FixedLengthArrayType.multi(elementType, remainingDimensions);
+    return FixedLengthArrayType(nestedArray, dimensions.first);
+  }
+
   String get cType => elementType.cType;
-  String get dartCType => "Array<${elementType.dartType}>";
+  String get dartCType => "Array<${elementType.dartCType}>";
   String get dartType => "Array<${elementType.dartCType}>";
-  String get dartStructFieldAnnotation => "@Array($length)";
+
+  String get dartStructFieldAnnotation {
+    if (dimensions.length > 5) {
+      return "@Array.multi([${dimensions.join(", ")}])";
+    }
+    return "@Array(${dimensions.join(", ")})";
+  }
+
+  List<int> get dimensions {
+    final elementType = this.elementType;
+    if (elementType is FixedLengthArrayType) {
+      return [length, ...elementType.dimensions];
+    }
+    return [length];
+  }
+
+  bool get isMulti => elementType is FixedLengthArrayType;
 
   bool get hasSize => elementType.hasSize;
   int get size => elementType.size * length;
diff --git a/tests/ffi_2/generator/structs_by_value_tests_configuration.dart b/tests/ffi_2/generator/structs_by_value_tests_configuration.dart
index c681f8a..d0a22b0 100644
--- a/tests/ffi_2/generator/structs_by_value_tests_configuration.dart
+++ b/tests/ffi_2/generator/structs_by_value_tests_configuration.dart
@@ -310,6 +310,24 @@
 On x64, it will exhaust the integer registers with the 6th argument.
 The rest goes on the stack.
 On arm, arguments are 4 byte aligned."""),
+  FunctionType(
+      [
+        uint8,
+        struct8bytesInlineArrayMultiDimesional,
+        uint8,
+        struct8bytesInlineArrayMultiDimesional,
+        uint8,
+        struct8bytesInlineArrayMultiDimesional,
+        uint8
+      ],
+      uint32,
+      """
+Test multi dimensional inline array struct as argument."""),
+  FunctionType(
+      [uint8, structMultiDimensionalStruct, uint8],
+      uint32,
+      """
+Test struct in multi dimensional inline array."""),
   FunctionType(struct1byteInt.memberTypes, struct1byteInt, """
 Smallest struct with data."""),
   FunctionType(struct3bytesInt.memberTypes, struct3bytesInt, """
@@ -507,6 +525,10 @@
   struct16bytesFloatInlineNested,
   struct32bytesDoubleInlineNested,
   struct16bytesMixedInlineNested,
+  struct8bytesInlineArrayMultiDimesional,
+  struct32bytesInlineArrayMultiDimesional,
+  struct64bytesInlineArrayMultiDimesional,
+  structMultiDimensionalStruct,
 ];
 
 final struct1byteInt = StructType([int8]);
@@ -634,3 +656,19 @@
   FixedLengthArrayType(StructType([float, int16, int16]), 1),
   FixedLengthArrayType(int16, 2),
 ], "Struct16BytesMixed3");
+
+final struct8bytesInlineArrayMultiDimesional = StructType([
+  FixedLengthArrayType.multi(uint8, [2, 2, 2])
+]);
+
+final struct32bytesInlineArrayMultiDimesional = StructType([
+  FixedLengthArrayType.multi(uint8, [2, 2, 2, 2, 2])
+]);
+
+final struct64bytesInlineArrayMultiDimesional = StructType([
+  FixedLengthArrayType.multi(uint8, [2, 2, 2, 2, 2, 2])
+]);
+
+final structMultiDimensionalStruct = StructType([
+  FixedLengthArrayType.multi(struct1byteInt, [2, 2])
+]);
diff --git a/tests/ffi_2/generator/structs_by_value_tests_generator.dart b/tests/ffi_2/generator/structs_by_value_tests_generator.dart
index 7ebb535..51f51ab 100644
--- a/tests/ffi_2/generator/structs_by_value_tests_generator.dart
+++ b/tests/ffi_2/generator/structs_by_value_tests_generator.dart
@@ -332,7 +332,7 @@
       case FixedLengthArrayType:
         final this_ = this as FixedLengthArrayType;
         return """
-for(int i = 0; i < ${this_.length}; i++){
+for (int i = 0; i < ${this_.length}; i++){
   ${this_.elementType.dartExpectsStatements("$expected[i]", "$actual[i]")}
 }
 """;
@@ -370,7 +370,7 @@
       case FixedLengthArrayType:
         final this_ = this as FixedLengthArrayType;
         return """
-for(intptr_t i = 0; i < ${this_.length}; i++){
+for (intptr_t i = 0; i < ${this_.length}; i++){
   ${this_.elementType.cExpectsStatements("$expected[i]", "$actual[i]")}
 }
 """;
@@ -397,7 +397,7 @@
       case FixedLengthArrayType:
         final this_ = this as FixedLengthArrayType;
         return """
-for(intptr_t i = 0; i < ${this_.length}; i++){
+for (intptr_t i = 0; i < ${this_.length}; i++){
   ${this_.elementType.cExpectsZeroStatements("$actual[i]")}
 }
 """;
@@ -461,8 +461,18 @@
     }
     String toStringBody = members.map((m) {
       if (m.type is FixedLengthArrayType) {
-        final length = (m.type as FixedLengthArrayType).length;
-        return "\$\{[for (var i = 0; i < $length; i += 1) ${m.name}[i]]\}";
+        int dimensionNumber = 0;
+        String inlineFor = "";
+        String read = m.name;
+        String closing = "";
+        for (final dimension in (m.type as FixedLengthArrayType).dimensions) {
+          final i = "i$dimensionNumber";
+          inlineFor += "[for (var $i = 0; $i < $dimension; $i += 1)";
+          read += "[$i]";
+          closing += "]";
+          dimensionNumber++;
+        }
+        return "\$\{$inlineFor $read $closing\}";
       }
       return "\$\{${m.name}\}";
     }).join(", ");
diff --git a/tests/ffi_2/inline_array_multi_dimensional_test.dart b/tests/ffi_2/inline_array_multi_dimensional_test.dart
new file mode 100644
index 0000000..15d2059
--- /dev/null
+++ b/tests/ffi_2/inline_array_multi_dimensional_test.dart
@@ -0,0 +1,164 @@
+// 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.
+//
+// SharedObjects=ffi_test_functions
+
+import 'dart:ffi';
+
+import "package:expect/expect.dart";
+import 'package:ffi/ffi.dart';
+
+// Reuse struct definitions.
+import 'function_structs_by_value_generated_test.dart';
+
+void main() {
+  testSizeOf();
+  testLoad();
+  testLoadMultiAnnotation();
+  testStore();
+  testToString();
+  testRange();
+}
+
+void testSizeOf() {
+  Expect.equals(32, sizeOf<Struct32BytesInlineArrayMultiDimensionalInt>());
+  Expect.equals(64, sizeOf<Struct64BytesInlineArrayMultiDimensionalInt>());
+}
+
+/// Tests the load of nested `Array`s.
+///
+/// Only stores into arrays which do not have nested arrays.
+void testLoad() {
+  final Pointer<Struct32BytesInlineArrayMultiDimensionalInt> pointer = calloc();
+  final struct = pointer.ref;
+  final array = struct.a0;
+  for (int i = 0; i < 2; i++) {
+    for (int j = 0; j < 2; j++) {
+      for (int k = 0; k < 2; k++) {
+        for (int l = 0; l < 2; l++) {
+          for (int m = 0; m < 2; m++) {
+            array[i][j][k][l][m] = i + j + k + l + m;
+          }
+        }
+      }
+    }
+  }
+  for (int i = 0; i < 2; i++) {
+    for (int j = 0; j < 2; j++) {
+      for (int k = 0; k < 2; k++) {
+        for (int l = 0; l < 2; l++) {
+          for (int m = 0; m < 2; m++) {
+            Expect.equals(i + j + k + l + m, array[i][j][k][l][m]);
+          }
+        }
+      }
+    }
+  }
+  calloc.free(pointer);
+}
+
+/// Tests the load of nested `Array`s.
+///
+/// Only stores into arrays which do not have nested arrays.
+void testLoadMultiAnnotation() {
+  final Pointer<Struct64BytesInlineArrayMultiDimensionalInt> pointer = calloc();
+  final struct = pointer.ref;
+  final array = struct.a0;
+  for (int i = 0; i < 2; i++) {
+    for (int j = 0; j < 2; j++) {
+      for (int k = 0; k < 2; k++) {
+        for (int l = 0; l < 2; l++) {
+          for (int m = 0; m < 2; m++) {
+            for (int o = 0; o < 2; o++) {
+              array[i][j][k][l][m][o] = i + j + k + l + m + o;
+            }
+          }
+        }
+      }
+    }
+  }
+  for (int i = 0; i < 2; i++) {
+    for (int j = 0; j < 2; j++) {
+      for (int k = 0; k < 2; k++) {
+        for (int l = 0; l < 2; l++) {
+          for (int m = 0; m < 2; m++) {
+            for (int o = 0; o < 2; o++) {
+              Expect.equals(i + j + k + l + m + o, array[i][j][k][l][m][o]);
+            }
+          }
+        }
+      }
+    }
+  }
+  calloc.free(pointer);
+}
+
+void testStore() {
+  final Pointer<Struct32BytesInlineArrayMultiDimensionalInt> pointer = calloc();
+  final struct = pointer.ref;
+  final array = struct.a0;
+  for (int i = 0; i < 2; i++) {
+    for (int j = 0; j < 2; j++) {
+      for (int k = 0; k < 2; k++) {
+        for (int l = 0; l < 2; l++) {
+          for (int m = 0; m < 2; m++) {
+            array[i][j][k][l][m] = i + j + k + l + m;
+          }
+        }
+      }
+    }
+  }
+  array[0] = array[1]; // Copy many things.
+  for (int j = 0; j < 2; j++) {
+    for (int k = 0; k < 2; k++) {
+      for (int l = 0; l < 2; l++) {
+        for (int m = 0; m < 2; m++) {
+          Expect.equals(array[1][j][k][l][m], array[0][j][k][l][m]);
+        }
+      }
+    }
+  }
+  calloc.free(pointer);
+}
+
+// // Tests the toString of the test generator.
+void testToString() {
+  final Pointer<Struct32BytesInlineArrayMultiDimensionalInt> pointer = calloc();
+  final struct = pointer.ref;
+  final array = struct.a0;
+  for (int i = 0; i < 2; i++) {
+    for (int j = 0; j < 2; j++) {
+      for (int k = 0; k < 2; k++) {
+        for (int l = 0; l < 2; l++) {
+          for (int m = 0; m < 2; m++) {
+            array[i][j][k][l][m] = 16 * i + 8 * j + 4 * k + 2 * l + m;
+          }
+        }
+      }
+    }
+  }
+  Expect.equals(
+      "([[[[[0, 1], [2, 3]], [[4, 5], [6, 7]]], [[[8, 9], [10, 11]], [[12, 13], [14, 15]]]], [[[[16, 17], [18, 19]], [[20, 21], [22, 23]]], [[[24, 25], [26, 27]], [[28, 29], [30, 31]]]]])",
+      struct.toString());
+  calloc.free(pointer);
+}
+
+void testRange() {
+  final pointer = calloc<Struct32BytesInlineArrayMultiDimensionalInt>();
+  final struct = pointer.ref;
+  final array = struct.a0;
+  array[0];
+  array[1];
+  Expect.throws(() => array[-1]);
+  Expect.throws(() => array[-1] = array[1]);
+  Expect.throws(() => array[2]);
+  Expect.throws(() => array[2] = array[1]);
+  array[0][0];
+  array[0][1];
+  Expect.throws(() => array[0][-1]);
+  Expect.throws(() => array[0][-1] = array[0][1]);
+  Expect.throws(() => array[0][2]);
+  Expect.throws(() => array[0][2] = array[0][1]);
+  calloc.free(pointer);
+}
diff --git a/tests/ffi_2/inline_array_test.dart b/tests/ffi_2/inline_array_test.dart
index 518d62e..868d117 100644
--- a/tests/ffi_2/inline_array_test.dart
+++ b/tests/ffi_2/inline_array_test.dart
@@ -30,11 +30,11 @@
 void testLoad() {
   final pointer = calloc<Struct8BytesInlineArrayInt>();
   final struct = pointer.ref;
-  final cArray = struct.a0;
+  final array = struct.a0;
   pointer.cast<Uint8>()[0] = 42;
   pointer.cast<Uint8>()[7] = 3;
-  Expect.equals(42, cArray[0]);
-  Expect.equals(3, cArray[7]);
+  Expect.equals(42, array[0]);
+  Expect.equals(3, array[7]);
   calloc.free(pointer);
 }
 
@@ -55,9 +55,9 @@
 void testToString() {
   final pointer = calloc<Struct8BytesInlineArrayInt>();
   final struct = pointer.ref;
-  final cArray = struct.a0;
+  final array = struct.a0;
   for (var i = 0; i < 8; i++) {
-    cArray[i] = i;
+    array[i] = i;
   }
   Expect.equals("([0, 1, 2, 3, 4, 5, 6, 7])", struct.toString());
   calloc.free(pointer);
@@ -77,14 +77,14 @@
 void testRange() {
   final pointer = calloc<Struct8BytesInlineArrayInt>();
   final struct = pointer.ref;
-  final cArray = struct.a0;
-  cArray[0] = 1;
-  Expect.equals(1, cArray[0]);
-  cArray[7] = 7;
-  Expect.equals(7, cArray[7]);
-  Expect.throws(() => cArray[-1]);
-  Expect.throws(() => cArray[-1] = 0);
-  Expect.throws(() => cArray[8]);
-  Expect.throws(() => cArray[8] = 0);
+  final array = struct.a0;
+  array[0] = 1;
+  Expect.equals(1, array[0]);
+  array[7] = 7;
+  Expect.equals(7, array[7]);
+  Expect.throws(() => array[-1]);
+  Expect.throws(() => array[-1] = 0);
+  Expect.throws(() => array[8]);
+  Expect.throws(() => array[8] = 0);
   calloc.free(pointer);
 }
diff --git a/tests/ffi_2/vmspecific_static_checks_test.dart b/tests/ffi_2/vmspecific_static_checks_test.dart
index 217df34..978fb2d 100644
--- a/tests/ffi_2/vmspecific_static_checks_test.dart
+++ b/tests/ffi_2/vmspecific_static_checks_test.dart
@@ -675,8 +675,29 @@
 }
 
 class TestStruct1402 extends Struct {
-  @Array(8) //# 1402: compile-time error
+  @Array(8, 8, 8) //# 1402: compile-time error
   Array<Array<Uint8>> a0; //# 1402: compile-time error
 
   Pointer<Uint8> notEmpty;
 }
+
+class TestStruct1403 extends Struct {
+  @Array(8, 8) //# 1403: compile-time error
+  Array<Array<Array<Uint8>>> a0; //# 1403: compile-time error
+
+  Pointer<Uint8> notEmpty;
+}
+
+class TestStruct1404 extends Struct {
+  @Array.multi([8, 8, 8]) //# 1404: compile-time error
+  Array<Array<Uint8>> a0; //# 1404: compile-time error
+
+  Pointer<Uint8> notEmpty;
+}
+
+class TestStruct1405 extends Struct {
+  @Array.multi([8, 8]) //# 1405: compile-time error
+  Array<Array<Array<Uint8>>> a0; //# 1405: compile-time error
+
+  Pointer<Uint8> notEmpty;
+}
diff --git a/tools/VERSION b/tools/VERSION
index 984173b..150d415 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 13
 PATCH 0
-PRERELEASE 120
+PRERELEASE 121
 PRERELEASE_PATCH 0
\ No newline at end of file