[cfe/ffi] Support missing `Abi`s in `NativeTypeCfe`

When ABI-specific integers are introduced, their mappings can be
partial. We need to account for this in the transformation and the code
we generate.

In the transformation, all sizes and offsets become nullable.
In the generated code we add `null` constants and a call to check
whether the value is not-null at runtime.

Note that with only this CL we can not generate nulls yet, because all
size and offset mappings are still complete.

TEST=pkg/front_end/testcases/nnbd/ffi*
TEST=tests/ffi*

Bug: https://github.com/dart-lang/sdk/issues/42563

Change-Id: I80d45f3f52001670bc0679a033f7daa22198d55e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/221631
Reviewed-by: Ryan Macnak <rmacnak@google.com>
diff --git a/pkg/vm/lib/transformations/ffi/common.dart b/pkg/vm/lib/transformations/ffi/common.dart
index 54ffd5c..42235ff 100644
--- a/pkg/vm/lib/transformations/ffi/common.dart
+++ b/pkg/vm/lib/transformations/ffi/common.dart
@@ -234,6 +234,7 @@
   final Procedure lookupFunctionTearoff;
   final Procedure getNativeFieldFunction;
   final Procedure reachabilityFenceFunction;
+  final Procedure checkAbiSpecificIntegerMappingFunction;
 
   late final InterfaceType nativeFieldWrapperClass1Type;
   late final InterfaceType voidType;
@@ -417,7 +418,9 @@
         getNativeFieldFunction = index.getTopLevelProcedure(
             'dart:nativewrappers', '_getNativeField'),
         reachabilityFenceFunction =
-            index.getTopLevelProcedure('dart:_internal', 'reachabilityFence') {
+            index.getTopLevelProcedure('dart:_internal', 'reachabilityFence'),
+        checkAbiSpecificIntegerMappingFunction = index.getTopLevelProcedure(
+            'dart:ffi', "_checkAbiSpecificIntegerMapping") {
     nativeFieldWrapperClass1Type = nativeFieldWrapperClass1Class.getThisType(
         coreTypes, Nullability.nonNullable);
     voidType = nativeTypesClasses[NativeType.kVoid]!
@@ -453,7 +456,7 @@
   /// [Bool]                               -> [bool]
   /// [Void]                               -> [void]
   /// [Pointer]<T>                         -> [Pointer]<T>
-  /// T extends [Pointer]                  -> T
+  /// T extends [Compound]                 -> T
   /// [Handle]                             -> [Object]
   /// [NativeFunction]<T1 Function(T2, T3) -> S1 Function(S2, S3)
   ///    where DartRepresentationOf(Tn) -> Sn
@@ -535,27 +538,42 @@
   InterfaceType _listOfIntType() => InterfaceType(
       listClass, Nullability.legacy, [coreTypes.intLegacyRawType]);
 
-  ConstantExpression intListConstantExpression(List<int> values) =>
+  ConstantExpression intListConstantExpression(List<int?> values) =>
       ConstantExpression(
-          ListConstant(coreTypes.intLegacyRawType,
-              [for (var v in values) IntConstant(v)]),
+          ListConstant(coreTypes.intLegacyRawType, [
+            for (var v in values)
+              if (v != null) IntConstant(v) else NullConstant()
+          ]),
           _listOfIntType());
 
   /// Expression that queries VM internals at runtime to figure out on which ABI
   /// we are.
-  Expression runtimeBranchOnLayout(Map<Abi, int> values) {
-    return InstanceInvocation(
+  Expression runtimeBranchOnLayout(Map<Abi, int?> values) {
+    final result = InstanceInvocation(
         InstanceAccessKind.Instance,
         intListConstantExpression([
-          for (final abi in Abi.values) values[abi]!,
+          for (final abi in Abi.values) values[abi],
         ]),
         listElementAt.name,
         Arguments([StaticInvocation(abiMethod, Arguments([]))]),
         interfaceTarget: listElementAt,
         functionType: Substitution.fromInterfaceType(_listOfIntType())
             .substituteType(listElementAt.getterType) as FunctionType);
+    if (values.isPartial) {
+      return checkAbiSpecificIntegerMapping(result);
+    }
+    return result;
   }
 
+  Expression checkAbiSpecificIntegerMapping(Expression nullableExpression) =>
+      StaticInvocation(
+        checkAbiSpecificIntegerMappingFunction,
+        Arguments(
+          [nullableExpression],
+          types: [InterfaceType(intClass, Nullability.nonNullable)],
+        ),
+      );
+
   /// Generates an expression that returns a new `Pointer<dartType>` offset
   /// by [offset] from [pointer].
   ///
@@ -819,3 +837,8 @@
   }
   return false;
 }
+
+extension on Map<Abi, Object?> {
+  bool get isPartial =>
+      [for (final abi in Abi.values) this[abi]].contains(null);
+}
diff --git a/pkg/vm/lib/transformations/ffi/definitions.dart b/pkg/vm/lib/transformations/ffi/definitions.dart
index d80ef22..fbf2052 100644
--- a/pkg/vm/lib/transformations/ffi/definitions.dart
+++ b/pkg/vm/lib/transformations/ffi/definitions.dart
@@ -448,9 +448,8 @@
         // This class is invalid, but continue reporting other errors on it.
         success = false;
       } else {
-        final DartType nativeType = InterfaceType(
-            nativeTypesClasses[_getFieldType(nativeTypeAnnos.first)!]!,
-            Nullability.legacy);
+        final DartType nativeType =
+            InterfaceType(nativeTypeAnnos.first, Nullability.legacy);
         final DartType? shouldBeDartType = convertNativeTypeToDartType(
             nativeType,
             allowCompounds: true,
@@ -704,7 +703,6 @@
 
   static const vmFfiStructFields = "vm:ffi:struct-fields";
 
-  // return value is nullable.
   InstanceConstant? _compoundAnnotatedFields(Class node) {
     for (final annotation in node.annotations) {
       if (annotation is ConstantExpression) {
@@ -774,7 +772,6 @@
     return UnionNativeTypeCfe(compoundClass, members);
   }
 
-  // packing is `int?`.
   void _annoteCompoundWithFields(
       Class node, List<NativeTypeCfe> types, int? packing) {
     List<Constant> constants =
@@ -794,8 +791,13 @@
         InterfaceType(pragmaClass, Nullability.nonNullable, [])));
   }
 
-  void _generateMethodsForField(Class node, Field field, NativeTypeCfe type,
-      Map<Abi, int> offsets, bool unalignedAccess, IndexedClass? indexedClass) {
+  void _generateMethodsForField(
+      Class node,
+      Field field,
+      NativeTypeCfe type,
+      Map<Abi, int?> offsets,
+      bool unalignedAccess,
+      IndexedClass? indexedClass) {
     // TODO(johnniwinther): Avoid passing [indexedClass]. When compiling
     // incrementally, [field] should already carry the references from
     // [indexedClass].
@@ -846,7 +848,7 @@
   /// If sizes are not supplied still emits a field so that the use site
   /// transformer can still rewrite to it.
   void _addSizeOfField(Class compound, IndexedClass? indexedClass,
-      [Map<Abi, int>? sizes = null]) {
+      [Map<Abi, int?>? sizes = null]) {
     if (sizes == null) {
       sizes = {for (var abi in Abi.values) abi: 0};
     }
diff --git a/pkg/vm/lib/transformations/ffi/native_type_cfe.dart b/pkg/vm/lib/transformations/ffi/native_type_cfe.dart
index 9784ac9..862d1bd 100644
--- a/pkg/vm/lib/transformations/ffi/native_type_cfe.dart
+++ b/pkg/vm/lib/transformations/ffi/native_type_cfe.dart
@@ -52,12 +52,12 @@
   }
 
   /// The size in bytes per [Abi].
-  Map<Abi, int> get size;
+  Map<Abi, int?> get size;
 
   /// The alignment inside structs in bytes per [Abi].
   ///
   /// This is not the alignment on stack, this is only calculated in the VM.
-  Map<Abi, int> get alignment;
+  Map<Abi, int?> get alignment;
 
   /// Generates a Constant representing the type which is consumed by the VM.
   ///
@@ -70,7 +70,7 @@
   ///
   /// Takes [transformer] to be able to lookup classes and methods.
   ReturnStatement generateGetterStatement(DartType dartType, int fileOffset,
-      Map<Abi, int> offsets, bool unalignedAccess, FfiTransformer transformer);
+      Map<Abi, int?> offsets, bool unalignedAccess, FfiTransformer transformer);
 
   /// Generates the return statement for a compound field setter with this type.
   ///
@@ -78,7 +78,7 @@
   ReturnStatement generateSetterStatement(
       DartType dartType,
       int fileOffset,
-      Map<Abi, int> offsets,
+      Map<Abi, int?> offsets,
       bool unalignedAccess,
       VariableDeclaration argument,
       FfiTransformer transformer);
@@ -90,7 +90,7 @@
   InvalidNativeTypeCfe(this.reason);
 
   @override
-  Map<Abi, int> get alignment => throw reason;
+  Map<Abi, int?> get alignment => throw reason;
 
   @override
   Constant generateConstant(FfiTransformer transformer) => throw reason;
@@ -99,7 +99,7 @@
   ReturnStatement generateGetterStatement(
           DartType dartType,
           int fileOffset,
-          Map<Abi, int> offsets,
+          Map<Abi, int?> offsets,
           bool unalignedAccess,
           FfiTransformer transformer) =>
       throw reason;
@@ -108,14 +108,14 @@
   ReturnStatement generateSetterStatement(
           DartType dartType,
           int fileOffset,
-          Map<Abi, int> offsets,
+          Map<Abi, int?> offsets,
           bool unalignedAccess,
           VariableDeclaration argument,
           FfiTransformer transformer) =>
       throw reason;
 
   @override
-  Map<Abi, int> get size => throw reason;
+  Map<Abi, int?> get size => throw reason;
 }
 
 class PrimitiveNativeTypeCfe implements NativeTypeCfe {
@@ -126,7 +126,7 @@
   PrimitiveNativeTypeCfe(this.nativeType, this.clazz);
 
   @override
-  Map<Abi, int> get size {
+  Map<Abi, int?> get size {
     final int size = nativeTypeSizes[nativeType]!;
     if (size == WORD_SIZE) {
       return wordSize;
@@ -147,7 +147,7 @@
   bool get isFloat =>
       nativeType == NativeType.kFloat || nativeType == NativeType.kDouble;
 
-  bool isUnaligned(Map<Abi, int> offsets) {
+  bool isUnaligned(Map<Abi, int?> offsets) {
     final alignments = alignment;
     for (final abi in offsets.keys) {
       final offset = offsets[abi]!;
@@ -168,7 +168,7 @@
   ReturnStatement generateGetterStatement(
           DartType dartType,
           int fileOffset,
-          Map<Abi, int> offsets,
+          Map<Abi, int?> offsets,
           bool unalignedAccess,
           FfiTransformer transformer) =>
       ReturnStatement(StaticInvocation(
@@ -191,7 +191,7 @@
   ReturnStatement generateSetterStatement(
           DartType dartType,
           int fileOffset,
-          Map<Abi, int> offsets,
+          Map<Abi, int?> offsets,
           bool unalignedAccess,
           VariableDeclaration argument,
           FfiTransformer transformer) =>
@@ -210,10 +210,10 @@
 
 class PointerNativeTypeCfe implements NativeTypeCfe {
   @override
-  Map<Abi, int> get size => wordSize;
+  Map<Abi, int?> get size => wordSize;
 
   @override
-  Map<Abi, int> get alignment => wordSize;
+  Map<Abi, int?> get alignment => wordSize;
 
   @override
   Constant generateConstant(FfiTransformer transformer) => TypeLiteralConstant(
@@ -231,7 +231,7 @@
   ReturnStatement generateGetterStatement(
           DartType dartType,
           int fileOffset,
-          Map<Abi, int> offsets,
+          Map<Abi, int?> offsets,
           bool unalignedAccess,
           FfiTransformer transformer) =>
       ReturnStatement(StaticInvocation(
@@ -259,7 +259,7 @@
   ReturnStatement generateSetterStatement(
           DartType dartType,
           int fileOffset,
-          Map<Abi, int> offsets,
+          Map<Abi, int?> offsets,
           bool unalignedAccess,
           VariableDeclaration argument,
           FfiTransformer transformer) =>
@@ -281,15 +281,15 @@
 /// The layout of a `Struct` or `Union` in one [Abi].
 class CompoundLayout {
   /// Size of the entire struct or union.
-  final int size;
+  final int? size;
 
   /// Alignment of struct or union when nested in a struct.
-  final int alignment;
+  final int? alignment;
 
   /// Offset in bytes for each field, indexed by field number.
   ///
   /// Always 0 for unions.
-  final List<int> offsets;
+  final List<int?> offsets;
 
   CompoundLayout(this.size, this.alignment, this.offsets);
 }
@@ -304,11 +304,11 @@
   CompoundNativeTypeCfe._(this.clazz, this.members, this.layout);
 
   @override
-  Map<Abi, int> get size =>
+  Map<Abi, int?> get size =>
       layout.map((abi, layout) => MapEntry(abi, layout.size));
 
   @override
-  Map<Abi, int> get alignment =>
+  Map<Abi, int?> get alignment =>
       layout.map((abi, layout) => MapEntry(abi, layout.alignment));
 
   @override
@@ -323,8 +323,12 @@
   /// );
   /// ```
   @override
-  ReturnStatement generateGetterStatement(DartType dartType, int fileOffset,
-      Map<Abi, int> offsets, bool unalignedAccess, FfiTransformer transformer) {
+  ReturnStatement generateGetterStatement(
+      DartType dartType,
+      int fileOffset,
+      Map<Abi, int?> offsets,
+      bool unalignedAccess,
+      FfiTransformer transformer) {
     final constructor = clazz.constructors
         .firstWhere((c) => c.name == Name("#fromTypedDataBase"));
 
@@ -351,7 +355,7 @@
   ReturnStatement generateSetterStatement(
           DartType dartType,
           int fileOffset,
-          Map<Abi, int> offsets,
+          Map<Abi, int?> offsets,
           bool unalignedAccess,
           VariableDeclaration argument,
           FfiTransformer transformer) =>
@@ -389,23 +393,23 @@
   // NativeStructType::FromNativeTypes.
   static CompoundLayout _calculateLayout(
       List<NativeTypeCfe> types, int? packing, Abi abi) {
-    int offset = 0;
-    final offsets = <int>[];
-    int structAlignment = 1;
+    int? offset = 0;
+    final offsets = <int?>[];
+    int? structAlignment = 1;
     for (int i = 0; i < types.length; i++) {
-      final int size = types[i].size[abi]!;
-      int alignment = types[i].alignment[abi]!;
-      if (packing != null && packing < alignment) {
-        alignment = packing;
+      final int? size = types[i].size[abi];
+      int? alignment = types[i].alignment[abi];
+      if (packing != null) {
+        alignment = min(packing, alignment);
       }
-      if (alignment > 0) {
-        offset = _alignOffset(offset, alignment);
+      if (alignment != null && alignment > 0) {
+        offset = offset.align(alignment);
       }
       offsets.add(offset);
       offset += size;
-      structAlignment = math.max(structAlignment, alignment);
+      structAlignment = max(structAlignment, alignment);
     }
-    final int size = _alignOffset(offset, structAlignment);
+    final int? size = offset.align(structAlignment);
     return CompoundLayout(size, structAlignment, offsets);
   }
 }
@@ -425,15 +429,15 @@
   // Keep consistent with runtime/vm/compiler/ffi/native_type.cc
   // NativeUnionType::FromNativeTypes.
   static CompoundLayout _calculateLayout(List<NativeTypeCfe> types, Abi abi) {
-    int unionSize = 1;
-    int unionAlignment = 1;
+    int? unionSize = 1;
+    int? unionAlignment = 1;
     for (int i = 0; i < types.length; i++) {
-      final int size = types[i].size[abi]!;
-      int alignment = types[i].alignment[abi]!;
-      unionSize = math.max(unionSize, size);
-      unionAlignment = math.max(unionAlignment, alignment);
+      final int? size = types[i].size[abi];
+      int? alignment = types[i].alignment[abi];
+      unionSize = max(unionSize, size);
+      unionAlignment = max(unionAlignment, alignment);
     }
-    final int size = _alignOffset(unionSize, unionAlignment);
+    final int? size = unionSize.align(unionAlignment);
     return CompoundLayout(size, unionAlignment, List.filled(types.length, 0));
   }
 }
@@ -476,11 +480,11 @@
   }
 
   @override
-  Map<Abi, int> get size =>
+  Map<Abi, int?> get size =>
       elementType.size.map((abi, size) => MapEntry(abi, size * length));
 
   @override
-  Map<Abi, int> get alignment => elementType.alignment;
+  Map<Abi, int?> get alignment => elementType.alignment;
 
   // Note that we flatten multi dimensional arrays.
   @override
@@ -500,8 +504,12 @@
   /// );
   /// ```
   @override
-  ReturnStatement generateGetterStatement(DartType dartType, int fileOffset,
-      Map<Abi, int> offsets, bool unalignedAccess, FfiTransformer transformer) {
+  ReturnStatement generateGetterStatement(
+      DartType dartType,
+      int fileOffset,
+      Map<Abi, int?> offsets,
+      bool unalignedAccess,
+      FfiTransformer transformer) {
     InterfaceType typeArgument =
         (dartType as InterfaceType).typeArguments.single as InterfaceType;
     return ReturnStatement(ConstructorInvocation(
@@ -531,7 +539,7 @@
   ReturnStatement generateSetterStatement(
           DartType dartType,
           int fileOffset,
-          Map<Abi, int> offsets,
+          Map<Abi, int?> offsets,
           bool unalignedAccess,
           VariableDeclaration argument,
           FfiTransformer transformer) =>
@@ -549,5 +557,71 @@
         ..fileOffset = fileOffset);
 }
 
-int _alignOffset(int offset, int alignment) =>
-    ((offset + alignment - 1) ~/ alignment) * alignment;
+extension on int? {
+  int? align(int? alignment) =>
+      ((this + alignment - 1) ~/ alignment) * alignment;
+
+  int? operator *(int? other) {
+    final this_ = this;
+    if (this_ == null) {
+      return null;
+    }
+    if (other == null) {
+      return null;
+    }
+    return this_ * other;
+  }
+
+  int? operator +(int? other) {
+    final this_ = this;
+    if (this_ == null) {
+      return null;
+    }
+    if (other == null) {
+      return null;
+    }
+    return this_ + other;
+  }
+
+  int? operator -(int? other) {
+    final this_ = this;
+    if (this_ == null) {
+      return null;
+    }
+    if (other == null) {
+      return null;
+    }
+    return this_ - other;
+  }
+
+  int? operator ~/(int? other) {
+    final this_ = this;
+    if (this_ == null) {
+      return null;
+    }
+    if (other == null) {
+      return null;
+    }
+    return this_ ~/ other;
+  }
+}
+
+int? max(int? a, int? b) {
+  if (a == null) {
+    return null;
+  }
+  if (b == null) {
+    return null;
+  }
+  return math.max(a, b);
+}
+
+int? min(int? a, int? b) {
+  if (a == null) {
+    return null;
+  }
+  if (b == null) {
+    return null;
+  }
+  return math.min(a, b);
+}
diff --git a/sdk/lib/_internal/vm/lib/ffi_patch.dart b/sdk/lib/_internal/vm/lib/ffi_patch.dart
index 14180b6..3af29c3 100644
--- a/sdk/lib/_internal/vm/lib/ffi_patch.dart
+++ b/sdk/lib/_internal/vm/lib/ffi_patch.dart
@@ -448,6 +448,16 @@
         Pointer<Pointer<S>> pointer, int index) =>
     Pointer.fromAddress(pointer.address + _intPtrSize * index);
 
+@pragma("vm:prefer-inline")
+@pragma("vm:entry-point")
+T _checkAbiSpecificIntegerMapping<T>(T? object) {
+  if (object == null) {
+    throw ArgumentError(
+        'AbiSpecificInteger is missing mapping for "${Abi.current()}".');
+  }
+  return object;
+}
+
 extension NativeFunctionPointer<NF extends Function>
     on Pointer<NativeFunction<NF>> {
   @patch