[vm/ffi] Don't tree-shake `Finalizable` members.

This CL prevents treeshaking of members that have type `Finalizable`, `Future<Finalizable>` or `FutureOr<Finalizable>`.

All `Finalizable` members are kept. Even the ones which are not
members of `Finalizable`s. This is in line with the logic to keep
all `Finalizable` arguments/variables alive.

Moreover, this CL adds AOT tests to the FFI transformer tests so that
we catch differences between AOT/JIT in the future.

Closes: https://github.com/dart-lang/sdk/issues/49643

TEST=pkg/vm/test/transformations/ffi_test.dart
TEST=pkg/vm/testcases/transformations/ffi/finalizable_member.dart

Change-Id: I14003314b9f23692fee30d1c3eef1bdcd27ed1ec
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/254904
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
diff --git a/pkg/vm/lib/transformations/ffi/finalizable.dart b/pkg/vm/lib/transformations/ffi/finalizable.dart
index 4fe96f3..b59786b 100644
--- a/pkg/vm/lib/transformations/ffi/finalizable.dart
+++ b/pkg/vm/lib/transformations/ffi/finalizable.dart
@@ -411,41 +411,17 @@
     return newStatement;
   }
 
-  /// Cache for [isFinalizable].
+  /// Cache for [_isFinalizable].
   ///
   /// Speeds up the type checks by about a factor of 2 on Flutter Gallery.
   Map<DartType, bool> _isFinalizableCache = {};
 
   /// Whether [type] is something that subtypes `FutureOr<Finalizable?>?`.
-  bool _isFinalizable(DartType type) {
-    final cached = _isFinalizableCache[type];
-    if (cached != null) {
-      return cached;
-    }
-
-    final finalizableType = FutureOrType(
-        InterfaceType(finalizableClass, Nullability.nullable),
-        Nullability.nullable);
-    if (!env.isSubtypeOf(
-      type,
-      finalizableType,
-      SubtypeCheckMode.withNullabilities,
-    )) {
-      _isFinalizableCache[type] = false;
-      return false;
-    }
-
-    // Exclude never types.
-    final futureOfNeverType =
-        FutureOrType(NeverType.nullable(), Nullability.nullable);
-    final result = !env.isSubtypeOf(
-      type,
-      futureOfNeverType,
-      SubtypeCheckMode.ignoringNullabilities,
-    );
-    _isFinalizableCache[type] = result;
-    return result;
-  }
+  bool _isFinalizable(DartType type) => type.isFinalizable(
+        finalizableClass: finalizableClass,
+        typeEnvironment: env,
+        cache: _isFinalizableCache,
+      );
 
   bool _thisIsFinalizableFromMember(Member member) {
     final enclosingClass_ = member.enclosingClass;
@@ -896,3 +872,41 @@
     return false;
   }
 }
+
+extension FinalizableDartType on DartType {
+  /// Whether `this` is something that subtypes `FutureOr<Finalizable?>?`.
+  bool isFinalizable({
+    required Class finalizableClass,
+    required TypeEnvironment typeEnvironment,
+    Map<DartType, bool>? cache,
+  }) {
+    final type = this;
+    final cached = cache?[type];
+    if (cached != null) {
+      return cached;
+    }
+
+    final finalizableType = FutureOrType(
+        InterfaceType(finalizableClass, Nullability.nullable),
+        Nullability.nullable);
+    if (!typeEnvironment.isSubtypeOf(
+      type,
+      finalizableType,
+      SubtypeCheckMode.withNullabilities,
+    )) {
+      cache?[type] = false;
+      return false;
+    }
+
+    // Exclude never types.
+    final futureOfNeverType =
+        FutureOrType(NeverType.nullable(), Nullability.nullable);
+    final result = !typeEnvironment.isSubtypeOf(
+      type,
+      futureOfNeverType,
+      SubtypeCheckMode.ignoringNullabilities,
+    );
+    cache?[type] = result;
+    return result;
+  }
+}
diff --git a/pkg/vm/lib/transformations/type_flow/finalizable_types.dart b/pkg/vm/lib/transformations/type_flow/finalizable_types.dart
new file mode 100644
index 0000000..6fe969c
--- /dev/null
+++ b/pkg/vm/lib/transformations/type_flow/finalizable_types.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2022, 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:kernel/ast.dart';
+import 'package:kernel/class_hierarchy.dart';
+import 'package:kernel/core_types.dart';
+import 'package:kernel/library_index.dart';
+import 'package:kernel/type_environment.dart';
+import 'package:vm/transformations/ffi/finalizable.dart'
+    show FinalizableDartType;
+
+/// Provides insights into `Finalizable`s.
+class FinalizableTypes {
+  final TypeEnvironment _env;
+  final Class _finalizableClass;
+
+  FinalizableTypes(
+    CoreTypes coreTypes,
+    LibraryIndex index,
+    ClassHierarchy classHierarchy,
+  )   : _env = TypeEnvironment(coreTypes, classHierarchy),
+        _finalizableClass = index.getClass('dart:ffi', 'Finalizable');
+
+  bool isFieldFinalizable(Field field) => _isFinalizable(field.type);
+
+  /// Cache for [_isFinalizable].
+  Map<DartType, bool> _isFinalizableCache = {};
+
+  /// Whether [type] is something that subtypes `FutureOr<Finalizable?>?`.
+  bool _isFinalizable(DartType type) => type.isFinalizable(
+        finalizableClass: _finalizableClass,
+        typeEnvironment: _env,
+        cache: _isFinalizableCache,
+      );
+}
diff --git a/pkg/vm/lib/transformations/type_flow/transformer.dart b/pkg/vm/lib/transformations/type_flow/transformer.dart
index 5fc5ada..23a6fce 100644
--- a/pkg/vm/lib/transformations/type_flow/transformer.dart
+++ b/pkg/vm/lib/transformations/type_flow/transformer.dart
@@ -6,34 +6,35 @@
 
 import 'dart:core' hide Type;
 
-import 'package:kernel/target/targets.dart';
 import 'package:kernel/ast.dart' hide Statement, StatementVisitor;
 import 'package:kernel/ast.dart' as ast show Statement;
-import 'package:kernel/clone.dart' show CloneVisitorNotMembers;
-import 'package:kernel/core_types.dart' show CoreTypes;
 import 'package:kernel/class_hierarchy.dart'
     show ClassHierarchy, ClosedWorldClassHierarchy;
+import 'package:kernel/clone.dart' show CloneVisitorNotMembers;
+import 'package:kernel/core_types.dart' show CoreTypes;
 import 'package:kernel/library_index.dart' show LibraryIndex;
+import 'package:kernel/target/targets.dart';
 import 'package:kernel/type_environment.dart';
 
-import 'analysis.dart';
-import 'calls.dart';
-import 'signature_shaking.dart';
-import 'protobuf_handler.dart' show ProtobufHandler;
-import 'rta.dart' show RapidTypeAnalysis;
-import 'summary.dart';
-import 'table_selector_assigner.dart';
-import 'types.dart';
-import 'unboxing_info.dart';
-import 'utils.dart';
-import '../pragma.dart';
-import '../devirtualization.dart' show Devirtualization;
 import '../../metadata/direct_call.dart';
 import '../../metadata/inferred_type.dart';
 import '../../metadata/procedure_attributes.dart';
 import '../../metadata/table_selector.dart';
 import '../../metadata/unboxing_info.dart';
 import '../../metadata/unreachable.dart';
+import '../devirtualization.dart' show Devirtualization;
+import '../pragma.dart';
+import 'analysis.dart';
+import 'calls.dart';
+import 'finalizable_types.dart';
+import 'protobuf_handler.dart' show ProtobufHandler;
+import 'rta.dart' show RapidTypeAnalysis;
+import 'signature_shaking.dart';
+import 'summary.dart';
+import 'table_selector_assigner.dart';
+import 'types.dart';
+import 'unboxing_info.dart';
+import 'utils.dart';
 
 const bool kDumpClassHierarchy =
     const bool.fromEnvironment('global.type.flow.dump.class.hierarchy');
@@ -118,7 +119,8 @@
 
   final transformsStopWatch = new Stopwatch()..start();
 
-  final treeShaker = new TreeShaker(component, typeFlowAnalysis,
+  final treeShaker = new TreeShaker(
+      component, typeFlowAnalysis, coreTypes, hierarchy,
       treeShakeWriteOnlyFields: treeShakeWriteOnlyFields);
   treeShaker.transformComponent(component);
 
@@ -711,14 +713,21 @@
   final Set<Member> _usedMembers = new Set<Member>();
   final Set<Extension> _usedExtensions = new Set<Extension>();
   final Set<Typedef> _usedTypedefs = new Set<Typedef>();
+  final FinalizableTypes _finalizableTypes;
   late final FieldMorpher fieldMorpher;
   late final _TreeShakerTypeVisitor typeVisitor;
   late final _TreeShakerConstantVisitor constantVisitor;
   late final _TreeShakerPass1 _pass1;
   late final _TreeShakerPass2 _pass2;
 
-  TreeShaker(Component component, this.typeFlowAnalysis,
-      {this.treeShakeWriteOnlyFields: true}) {
+  TreeShaker(
+    Component component,
+    this.typeFlowAnalysis,
+    CoreTypes coreTypes,
+    ClassHierarchy hierarchy, {
+    this.treeShakeWriteOnlyFields: true,
+  }) : _finalizableTypes = new FinalizableTypes(
+            coreTypes, typeFlowAnalysis.libraryIndex, hierarchy) {
     fieldMorpher = new FieldMorpher(this);
     typeVisitor = new _TreeShakerTypeVisitor(this);
     constantVisitor = new _TreeShakerConstantVisitor(this, typeVisitor);
@@ -747,6 +756,7 @@
   bool isFieldSetterReachable(Field f) => typeFlowAnalysis.isFieldSetterUsed(f);
   bool isMemberReferencedFromNativeCode(Member m) =>
       typeFlowAnalysis.nativeCodeOracle.isMemberReferencedFromNativeCode(m);
+  bool isFieldFinalizable(Field f) => _finalizableTypes.isFieldFinalizable(f);
   bool isTypedefUsed(Typedef t) => _usedTypedefs.contains(t);
 
   bool retainField(Field f) =>
@@ -757,7 +767,8 @@
                   f.initializer != null &&
                   isFieldInitializerReachable(f) &&
                   mayHaveSideEffects(f.initializer!)) ||
-              (f.isLate && f.isFinal)) ||
+              (f.isLate && f.isFinal) ||
+              isFieldFinalizable(f)) ||
       isMemberReferencedFromNativeCode(f) ||
       _isInstanceFieldOfAllocatedEnum(f);
 
diff --git a/pkg/vm/test/common_test_utils.dart b/pkg/vm/test/common_test_utils.dart
index b657777..cadd34c 100644
--- a/pkg/vm/test/common_test_utils.dart
+++ b/pkg/vm/test/common_test_utils.dart
@@ -154,8 +154,13 @@
       i < expectedLines.length ? expectedLines[i] : '<END>');
 }
 
-void compareResultWithExpectationsFile(Uri source, String actual) {
-  final expectFile = new File(source.toFilePath() + '.expect');
+void compareResultWithExpectationsFile(
+  Uri source,
+  String actual, {
+  String expectFilePostfix = '',
+}) {
+  final expectFile =
+      new File('${source.toFilePath()}$expectFilePostfix.expect');
   final expected = expectFile.existsSync() ? expectFile.readAsStringSync() : '';
 
   if (actual != expected) {
diff --git a/pkg/vm/test/transformations/ffi_test.dart b/pkg/vm/test/transformations/ffi_test.dart
index 3f6dd75..a9fd860 100644
--- a/pkg/vm/test/transformations/ffi_test.dart
+++ b/pkg/vm/test/transformations/ffi_test.dart
@@ -10,9 +10,9 @@
 import 'package:kernel/kernel.dart';
 import 'package:kernel/target/targets.dart';
 import 'package:kernel/verifier.dart';
-
 import 'package:test/test.dart';
-
+import 'package:vm/kernel_front_end.dart'
+    show runGlobalTransformations, ErrorDetector;
 import 'package:vm/transformations/ffi/native.dart' show transformLibraries;
 
 import '../common_test_utils.dart';
@@ -25,7 +25,7 @@
       {List<Object>? context}) {/* nop */}
 }
 
-runTestCase(Uri source) async {
+runTestCaseJit(Uri source) async {
   final target = TestingVmTarget(TargetFlags());
 
   Component component = await compileTestCaseToKernelProgram(source,
@@ -48,6 +48,33 @@
   compareResultWithExpectationsFile(source, actual);
 }
 
+runTestCaseAot(Uri source) async {
+  final target = TestingVmTarget(TargetFlags(supportMirrors: false));
+
+  Component component = await compileTestCaseToKernelProgram(source,
+      target: target, experimentalFlags: ['generic-metadata']);
+
+  const bool useGlobalTypeFlowAnalysis = true;
+  const bool enableAsserts = false;
+  const bool useProtobufAwareTreeShakerV2 = true;
+  final nopErrorDetector = ErrorDetector();
+  runGlobalTransformations(
+    target,
+    component,
+    useGlobalTypeFlowAnalysis,
+    enableAsserts,
+    useProtobufAwareTreeShakerV2,
+    nopErrorDetector,
+    treeShakeWriteOnlyFields: true,
+  );
+
+  verifyComponent(component);
+
+  final actual = kernelLibraryToString(component.mainMethod!.enclosingLibrary);
+
+  compareResultWithExpectationsFile(source, actual, expectFilePostfix: '.aot');
+}
+
 void main(List<String> args) {
   assert(args.isEmpty || args.length == 1);
   String? filter;
@@ -63,7 +90,8 @@
         .reversed) {
       if (entry.path.endsWith(".dart") &&
           (filter == null || entry.path.contains(filter))) {
-        test(entry.path, () => runTestCase(entry.uri));
+        test(entry.path, () => runTestCaseJit(entry.uri));
+        test('${entry.path} aot', () => runTestCaseAot(entry.uri));
       }
     }
   });
diff --git a/pkg/vm/testcases/transformations/ffi/abi_specific_int.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/abi_specific_int.dart.aot.expect
new file mode 100644
index 0000000..877dc32
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/abi_specific_int.dart.aot.expect
@@ -0,0 +1,129 @@
+library #lib /*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";
+
+@#C8
+abstract class WChar extends ffi::AbiSpecificInteger /*hasConstConstructor*/  {
+[@vm.unboxing-info.metadata=()->i]  @#C11
+  static get #sizeOf() → core::int*
+    return #C15.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
+}
+@#C20
+class WCharStruct extends ffi::Struct {
+  constructor #fromTypedDataBase([@vm.inferred-type.metadata=dart.ffi::Pointer] core::Object #typedDataBase) → self::WCharStruct
+    : super ffi::Struct::_fromTypedDataBase(#typedDataBase)
+    ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2]  get a0() → core::int
+    return [@vm.inferred-type.metadata=int?] ffi::_loadAbiSpecificInt<self::WChar>([@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=dart.ffi::Pointer] this.{ffi::_Compound::_typedDataBase}{core::Object}, #C22.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] [@vm.unboxing-info.metadata=(i)->b]  set a0([@vm.inferred-type.metadata=dart.core::_Smi] core::int #externalFieldValue) → void
+    return [@vm.inferred-type.metadata=int?] ffi::_storeAbiSpecificInt<self::WChar>([@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=dart.ffi::Pointer] this.{ffi::_Compound::_typedDataBase}{core::Object}, #C22.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
+[@vm.unboxing-info.metadata=()->i]  @#C11
+  static get #sizeOf() → core::int*
+    return #C24.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
+}
+@#C29
+class WCharArrayStruct extends ffi::Struct {
+  constructor #fromTypedDataBase([@vm.inferred-type.metadata=dart.ffi::Pointer] core::Object #typedDataBase) → self::WCharArrayStruct
+    : super ffi::Struct::_fromTypedDataBase(#typedDataBase)
+    ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:3]  get a0() → ffi::Array<self::WChar>
+    return new ffi::Array::_<self::WChar>( block {
+      core::Object #typedDataBase = [@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=dart.ffi::Pointer] this.{ffi::_Compound::_typedDataBase}{core::Object};
+      core::int #offset = #C22.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
+    } =>#typedDataBase is ffi::Pointer<dynamic> ?{core::Object} [@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::_fromAddress<self::WChar>([@vm.direct-call.metadata=dart.core::_IntegerImplementation.+??] [@vm.inferred-type.metadata=int (skip check)] [@vm.direct-call.metadata=dart.ffi::Pointer.address] [@vm.inferred-type.metadata=int?] #typedDataBase.{ffi::Pointer::address}{core::int}.{core::num::+}(#offset){(core::num) → core::num}) : let typ::TypedData #typedData = _in::unsafeCast<typ::TypedData>(#typedDataBase) in throw "Attempt to execute code removed by Dart AOT compiler (TFA)", #C25, #C30);
+[@vm.unboxing-info.metadata=()->i]  @#C11
+  static get #sizeOf() → core::int*
+    return #C34.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
+}
+class _DummyAllocator extends core::Object implements ffi::Allocator /*hasConstConstructor*/  {
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:4,getterSelectorId:5] [@vm.unboxing-info.metadata=(i)->b]  method allocate<T extends ffi::NativeType>([@vm.inferred-type.metadata=int] core::int byteCount) → ffi::Pointer<self::_DummyAllocator::allocate::T> {
+    return [@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::Pointer::fromAddress<self::_DummyAllocator::allocate::T>(0);
+  }
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:6,getterSelectorId:7]  method free([@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::Pointer<ffi::NativeType> pointer) → void {}
+}
+static method main() → void {
+  self::testSizeOf();
+  self::testStoreLoad();
+  self::testStoreLoadIndexed();
+  self::testStruct();
+  self::testInlineArray();
+}
+static method testSizeOf() → void {
+  final core::int size = [@vm.inferred-type.metadata=dart.core::_Smi] self::WChar::#sizeOf;
+  core::print(size);
+}
+static method testStoreLoad() → void {
+  final ffi::Pointer<self::WChar> p = [@vm.direct-call.metadata=#lib::_DummyAllocator.allocate] [@vm.inferred-type.metadata=dart.ffi::Pointer? (skip check)] #C35.{ffi::Allocator::allocate}<self::WChar>([@vm.inferred-type.metadata=dart.core::_Smi] self::WChar::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer<self::WChar>};
+  ffi::_storeAbiSpecificInt<self::WChar>(p, #C21, 10);
+  core::print([@vm.inferred-type.metadata=int?] ffi::_loadAbiSpecificInt<self::WChar>(p, #C21));
+  [@vm.direct-call.metadata=#lib::_DummyAllocator.free] [@vm.inferred-type.metadata=!? (skip check)] #C35.{self::_DummyAllocator::free}(p){(ffi::Pointer<ffi::NativeType>) → void};
+}
+static method testStoreLoadIndexed() → void {
+  final ffi::Pointer<self::WChar> p = [@vm.direct-call.metadata=#lib::_DummyAllocator.allocate] [@vm.inferred-type.metadata=dart.ffi::Pointer? (skip check)] #C35.{ffi::Allocator::allocate}<self::WChar>([@vm.direct-call.metadata=dart.core::_IntegerImplementation.*] [@vm.inferred-type.metadata=int (skip check)] 2.{core::num::*}([@vm.inferred-type.metadata=dart.core::_Smi] self::WChar::#sizeOf){(core::num) → core::num}){(core::int, {alignment: core::int?}) → ffi::Pointer<self::WChar>};
+  ffi::_storeAbiSpecificIntAtIndex<self::WChar>(p, 0, 10);
+  ffi::_storeAbiSpecificIntAtIndex<self::WChar>(p, 1, 3);
+  core::print([@vm.inferred-type.metadata=int?] ffi::_loadAbiSpecificIntAtIndex<self::WChar>(p, 0));
+  core::print([@vm.inferred-type.metadata=int?] ffi::_loadAbiSpecificIntAtIndex<self::WChar>(p, 1));
+  [@vm.direct-call.metadata=#lib::_DummyAllocator.free] [@vm.inferred-type.metadata=!? (skip check)] #C35.{self::_DummyAllocator::free}(p){(ffi::Pointer<ffi::NativeType>) → void};
+}
+static method testStruct() → void {
+  final ffi::Pointer<self::WCharStruct> p = [@vm.direct-call.metadata=#lib::_DummyAllocator.allocate] [@vm.inferred-type.metadata=dart.ffi::Pointer? (skip check)] #C35.{ffi::Allocator::allocate}<self::WCharStruct>([@vm.inferred-type.metadata=dart.core::_Smi] self::WCharStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer<self::WCharStruct>};
+  [@vm.direct-call.metadata=#lib::WCharStruct.a0] [@vm.inferred-type.metadata=!? (skip check)] new self::WCharStruct::#fromTypedDataBase(p!).{self::WCharStruct::a0} = 1;
+  core::print([@vm.direct-call.metadata=#lib::WCharStruct.a0] [@vm.inferred-type.metadata=int?] new self::WCharStruct::#fromTypedDataBase(p!).{self::WCharStruct::a0}{core::int});
+  [@vm.direct-call.metadata=#lib::WCharStruct.a0] [@vm.inferred-type.metadata=!? (skip check)] new self::WCharStruct::#fromTypedDataBase(p!).{self::WCharStruct::a0} = 2;
+  core::print([@vm.direct-call.metadata=#lib::WCharStruct.a0] [@vm.inferred-type.metadata=int?] new self::WCharStruct::#fromTypedDataBase(p!).{self::WCharStruct::a0}{core::int});
+  [@vm.direct-call.metadata=#lib::_DummyAllocator.free] [@vm.inferred-type.metadata=!? (skip check)] #C35.{self::_DummyAllocator::free}(p){(ffi::Pointer<ffi::NativeType>) → void};
+}
+static method testInlineArray() → void {
+  final ffi::Pointer<self::WCharArrayStruct> p = [@vm.direct-call.metadata=#lib::_DummyAllocator.allocate] [@vm.inferred-type.metadata=dart.ffi::Pointer? (skip check)] #C35.{ffi::Allocator::allocate}<self::WCharArrayStruct>([@vm.inferred-type.metadata=dart.core::_Smi] self::WCharArrayStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer<self::WCharArrayStruct>};
+  final ffi::Array<self::WChar> array = [@vm.direct-call.metadata=#lib::WCharArrayStruct.a0] [@vm.inferred-type.metadata=dart.ffi::Array<#lib::WChar>] new self::WCharArrayStruct::#fromTypedDataBase(p!).{self::WCharArrayStruct::a0}{ffi::Array<self::WChar>};
+  for (core::int i = 0; [@vm.direct-call.metadata=dart.core::_IntegerImplementation.<] [@vm.inferred-type.metadata=dart.core::bool (skip check)] i.{core::num::<}(100){(core::num) → core::bool}; i = [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(1){(core::num) → core::int}) {
+    ffi::_storeAbiSpecificIntAtIndex<self::WChar>([@vm.direct-call.metadata=dart.ffi::Array._typedDataBase] array.{ffi::Array::_typedDataBase}{core::Object}, i, i);
+  }
+  for (core::int i = 0; [@vm.direct-call.metadata=dart.core::_IntegerImplementation.<] [@vm.inferred-type.metadata=dart.core::bool (skip check)] i.{core::num::<}(100){(core::num) → core::bool}; i = [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(1){(core::num) → core::int}) {
+    core::print([@vm.inferred-type.metadata=int?] ffi::_loadAbiSpecificIntAtIndex<self::WChar>([@vm.direct-call.metadata=dart.ffi::Array._typedDataBase] array.{ffi::Array::_typedDataBase}{core::Object}, i));
+  }
+  [@vm.direct-call.metadata=#lib::_DummyAllocator.free] [@vm.inferred-type.metadata=!? (skip check)] #C35.{self::_DummyAllocator::free}(p){(ffi::Pointer<ffi::NativeType>) → void};
+}
+constants  {
+  #C1 = "vm:ffi:abi-specific-mapping"
+  #C2 = TypeLiteralConstant(ffi::Uint32)
+  #C3 = TypeLiteralConstant(ffi::Uint64)
+  #C4 = TypeLiteralConstant(ffi::Int32)
+  #C5 = TypeLiteralConstant(ffi::Uint16)
+  #C6 = <core::Type?>[#C2, #C2, #C2, #C2, #C3, #C2, #C2, #C2, #C2, #C2, #C2, #C4, #C4, #C4, #C4, #C2, #C2, #C5, #C5, #C5]
+  #C7 = ffi::_FfiAbiSpecificMapping {nativeTypes:#C6}
+  #C8 = core::pragma {name:#C1, options:#C7}
+  #C9 = "vm:prefer-inline"
+  #C10 = null
+  #C11 = core::pragma {name:#C9, options:#C10}
+  #C12 = 4
+  #C13 = 8
+  #C14 = 2
+  #C15 = <core::int*>[#C12, #C12, #C12, #C12, #C13, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C12, #C14, #C14, #C14]
+  #C16 = "vm:ffi:struct-fields"
+  #C17 = TypeLiteralConstant(self::WChar)
+  #C18 = <core::Type>[#C17, #C17]
+  #C19 = ffi::_FfiStructLayout {fieldTypes:#C18, packing:#C10}
+  #C20 = core::pragma {name:#C16, options:#C19}
+  #C21 = 0
+  #C22 = <core::int*>[#C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21, #C21]
+  #C23 = 16
+  #C24 = <core::int*>[#C13, #C13, #C13, #C13, #C23, #C13, #C13, #C13, #C13, #C13, #C13, #C13, #C13, #C13, #C13, #C13, #C13, #C12, #C12, #C12]
+  #C25 = 100
+  #C26 = ffi::_FfiInlineArray {elementType:#C17, length:#C25}
+  #C27 = <core::Type>[#C26]
+  #C28 = ffi::_FfiStructLayout {fieldTypes:#C27, packing:#C10}
+  #C29 = core::pragma {name:#C16, options:#C28}
+  #C30 = <core::int*>[]
+  #C31 = 400
+  #C32 = 800
+  #C33 = 200
+  #C34 = <core::int*>[#C31, #C31, #C31, #C31, #C32, #C31, #C31, #C31, #C31, #C31, #C31, #C31, #C31, #C31, #C31, #C31, #C31, #C33, #C33, #C33]
+  #C35 = self::_DummyAllocator {}
+}
diff --git a/pkg/vm/testcases/transformations/ffi/abi_specific_int_incomplete.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/abi_specific_int_incomplete.dart.aot.expect
new file mode 100644
index 0000000..0359b64
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/abi_specific_int_incomplete.dart.aot.expect
@@ -0,0 +1,122 @@
+library #lib /*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";
+
+@#C6
+abstract class Incomplete extends ffi::AbiSpecificInteger /*hasConstConstructor*/  {
+[@vm.unboxing-info.metadata=()->i]  @#C8
+  static get #sizeOf() → core::int*
+    return [@vm.inferred-type.metadata=dart.core::_Smi (value: 4)] ffi::_checkAbiSpecificIntegerMapping<core::int>(#C10.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
+}
+@#C15
+class IncompleteStruct extends ffi::Struct {
+  constructor #fromTypedDataBase([@vm.inferred-type.metadata=dart.ffi::Pointer] core::Object #typedDataBase) → self::IncompleteStruct
+    : super ffi::Struct::_fromTypedDataBase(#typedDataBase)
+    ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2]  get a0() → core::int
+    return [@vm.inferred-type.metadata=int?] ffi::_loadAbiSpecificInt<self::Incomplete>([@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=dart.ffi::Pointer] this.{ffi::_Compound::_typedDataBase}{core::Object}, #C17.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] [@vm.unboxing-info.metadata=(i)->b]  set a0([@vm.inferred-type.metadata=dart.core::_Smi] core::int #externalFieldValue) → void
+    return [@vm.inferred-type.metadata=int?] ffi::_storeAbiSpecificInt<self::Incomplete>([@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=dart.ffi::Pointer] this.{ffi::_Compound::_typedDataBase}{core::Object}, #C17.{core::List::[]}(ffi::_abi()){(core::int) → core::int*}, #externalFieldValue);
+[@vm.unboxing-info.metadata=()->i]  @#C8
+  static get #sizeOf() → core::int*
+    return [@vm.inferred-type.metadata=dart.core::_Smi (value: 8)] ffi::_checkAbiSpecificIntegerMapping<core::int>(#C19.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
+}
+@#C24
+class IncompleteArrayStruct extends ffi::Struct {
+  constructor #fromTypedDataBase([@vm.inferred-type.metadata=dart.ffi::Pointer] core::Object #typedDataBase) → self::IncompleteArrayStruct
+    : super ffi::Struct::_fromTypedDataBase(#typedDataBase)
+    ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:3]  get a0() → ffi::Array<self::Incomplete>
+    return new ffi::Array::_<self::Incomplete>( block {
+      core::Object #typedDataBase = [@vm.direct-call.metadata=dart.ffi::_Compound._typedDataBase] [@vm.inferred-type.metadata=dart.ffi::Pointer] this.{ffi::_Compound::_typedDataBase}{core::Object};
+      core::int #offset = #C17.{core::List::[]}(ffi::_abi()){(core::int) → core::int*};
+    } =>#typedDataBase is ffi::Pointer<dynamic> ?{core::Object} [@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::_fromAddress<self::Incomplete>([@vm.direct-call.metadata=dart.core::_IntegerImplementation.+??] [@vm.inferred-type.metadata=int (skip check)] [@vm.direct-call.metadata=dart.ffi::Pointer.address] [@vm.inferred-type.metadata=int?] #typedDataBase.{ffi::Pointer::address}{core::int}.{core::num::+}(#offset){(core::num) → core::num}) : let typ::TypedData #typedData = _in::unsafeCast<typ::TypedData>(#typedDataBase) in throw "Attempt to execute code removed by Dart AOT compiler (TFA)", #C20, #C25);
+[@vm.unboxing-info.metadata=()->i]  @#C8
+  static get #sizeOf() → core::int*
+    return [@vm.inferred-type.metadata=dart.core::_Smi (value: 400)] ffi::_checkAbiSpecificIntegerMapping<core::int>(#C27.{core::List::[]}(ffi::_abi()){(core::int) → core::int*});
+}
+class _DummyAllocator extends core::Object implements ffi::Allocator /*hasConstConstructor*/  {
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:4,getterSelectorId:5] [@vm.unboxing-info.metadata=(i)->b]  method allocate<T extends ffi::NativeType>([@vm.inferred-type.metadata=int] core::int byteCount) → ffi::Pointer<self::_DummyAllocator::allocate::T> {
+    return [@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::Pointer::fromAddress<self::_DummyAllocator::allocate::T>(0);
+  }
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:6,getterSelectorId:7]  method free([@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::Pointer<ffi::NativeType> pointer) → void {}
+}
+static method main() → void {
+  self::testSizeOf();
+  self::testStoreLoad();
+  self::testStoreLoadIndexed();
+  self::testStruct();
+  self::testInlineArray();
+}
+static method testSizeOf() → void {
+  final core::int size = [@vm.inferred-type.metadata=dart.core::_Smi (value: 4)] self::Incomplete::#sizeOf;
+  core::print(size);
+}
+static method testStoreLoad() → void {
+  final ffi::Pointer<self::Incomplete> p = [@vm.direct-call.metadata=#lib::_DummyAllocator.allocate] [@vm.inferred-type.metadata=dart.ffi::Pointer? (skip check)] #C28.{ffi::Allocator::allocate}<self::Incomplete>([@vm.inferred-type.metadata=dart.core::_Smi (value: 4)] self::Incomplete::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer<self::Incomplete>};
+  ffi::_storeAbiSpecificInt<self::Incomplete>(p, #C16, 10);
+  core::print([@vm.inferred-type.metadata=int?] ffi::_loadAbiSpecificInt<self::Incomplete>(p, #C16));
+  [@vm.direct-call.metadata=#lib::_DummyAllocator.free] [@vm.inferred-type.metadata=!? (skip check)] #C28.{self::_DummyAllocator::free}(p){(ffi::Pointer<ffi::NativeType>) → void};
+}
+static method testStoreLoadIndexed() → void {
+  final ffi::Pointer<self::Incomplete> p = [@vm.direct-call.metadata=#lib::_DummyAllocator.allocate] [@vm.inferred-type.metadata=dart.ffi::Pointer? (skip check)] #C28.{ffi::Allocator::allocate}<self::Incomplete>([@vm.direct-call.metadata=dart.core::_IntegerImplementation.*] [@vm.inferred-type.metadata=int (skip check)] 2.{core::num::*}([@vm.inferred-type.metadata=dart.core::_Smi (value: 4)] self::Incomplete::#sizeOf){(core::num) → core::num}){(core::int, {alignment: core::int?}) → ffi::Pointer<self::Incomplete>};
+  ffi::_storeAbiSpecificIntAtIndex<self::Incomplete>(p, 0, 10);
+  ffi::_storeAbiSpecificIntAtIndex<self::Incomplete>(p, 1, 3);
+  core::print([@vm.inferred-type.metadata=int?] ffi::_loadAbiSpecificIntAtIndex<self::Incomplete>(p, 0));
+  core::print([@vm.inferred-type.metadata=int?] ffi::_loadAbiSpecificIntAtIndex<self::Incomplete>(p, 1));
+  [@vm.direct-call.metadata=#lib::_DummyAllocator.free] [@vm.inferred-type.metadata=!? (skip check)] #C28.{self::_DummyAllocator::free}(p){(ffi::Pointer<ffi::NativeType>) → void};
+}
+static method testStruct() → void {
+  final ffi::Pointer<self::IncompleteStruct> p = [@vm.direct-call.metadata=#lib::_DummyAllocator.allocate] [@vm.inferred-type.metadata=dart.ffi::Pointer? (skip check)] #C28.{ffi::Allocator::allocate}<self::IncompleteStruct>([@vm.inferred-type.metadata=dart.core::_Smi (value: 8)] self::IncompleteStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer<self::IncompleteStruct>};
+  [@vm.direct-call.metadata=#lib::IncompleteStruct.a0] [@vm.inferred-type.metadata=!? (skip check)] new self::IncompleteStruct::#fromTypedDataBase(p!).{self::IncompleteStruct::a0} = 1;
+  core::print([@vm.direct-call.metadata=#lib::IncompleteStruct.a0] [@vm.inferred-type.metadata=int?] new self::IncompleteStruct::#fromTypedDataBase(p!).{self::IncompleteStruct::a0}{core::int});
+  [@vm.direct-call.metadata=#lib::IncompleteStruct.a0] [@vm.inferred-type.metadata=!? (skip check)] new self::IncompleteStruct::#fromTypedDataBase(p!).{self::IncompleteStruct::a0} = 2;
+  core::print([@vm.direct-call.metadata=#lib::IncompleteStruct.a0] [@vm.inferred-type.metadata=int?] new self::IncompleteStruct::#fromTypedDataBase(p!).{self::IncompleteStruct::a0}{core::int});
+  [@vm.direct-call.metadata=#lib::_DummyAllocator.free] [@vm.inferred-type.metadata=!? (skip check)] #C28.{self::_DummyAllocator::free}(p){(ffi::Pointer<ffi::NativeType>) → void};
+}
+static method testInlineArray() → void {
+  final ffi::Pointer<self::IncompleteArrayStruct> p = [@vm.direct-call.metadata=#lib::_DummyAllocator.allocate] [@vm.inferred-type.metadata=dart.ffi::Pointer? (skip check)] #C28.{ffi::Allocator::allocate}<self::IncompleteArrayStruct>([@vm.inferred-type.metadata=dart.core::_Smi (value: 400)] self::IncompleteArrayStruct::#sizeOf){(core::int, {alignment: core::int?}) → ffi::Pointer<self::IncompleteArrayStruct>};
+  final ffi::Array<self::Incomplete> array = [@vm.direct-call.metadata=#lib::IncompleteArrayStruct.a0] [@vm.inferred-type.metadata=dart.ffi::Array<#lib::Incomplete>] new self::IncompleteArrayStruct::#fromTypedDataBase(p!).{self::IncompleteArrayStruct::a0}{ffi::Array<self::Incomplete>};
+  for (core::int i = 0; [@vm.direct-call.metadata=dart.core::_IntegerImplementation.<] [@vm.inferred-type.metadata=dart.core::bool (skip check)] i.{core::num::<}(100){(core::num) → core::bool}; i = [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(1){(core::num) → core::int}) {
+    ffi::_storeAbiSpecificIntAtIndex<self::Incomplete>([@vm.direct-call.metadata=dart.ffi::Array._typedDataBase] array.{ffi::Array::_typedDataBase}{core::Object}, i, i);
+  }
+  for (core::int i = 0; [@vm.direct-call.metadata=dart.core::_IntegerImplementation.<] [@vm.inferred-type.metadata=dart.core::bool (skip check)] i.{core::num::<}(100){(core::num) → core::bool}; i = [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(1){(core::num) → core::int}) {
+    core::print([@vm.inferred-type.metadata=int?] ffi::_loadAbiSpecificIntAtIndex<self::Incomplete>([@vm.direct-call.metadata=dart.ffi::Array._typedDataBase] array.{ffi::Array::_typedDataBase}{core::Object}, i));
+  }
+  [@vm.direct-call.metadata=#lib::_DummyAllocator.free] [@vm.inferred-type.metadata=!? (skip check)] #C28.{self::_DummyAllocator::free}(p){(ffi::Pointer<ffi::NativeType>) → void};
+}
+constants  {
+  #C1 = "vm:ffi:abi-specific-mapping"
+  #C2 = null
+  #C3 = TypeLiteralConstant(ffi::Uint32)
+  #C4 = <core::Type?>[#C2, #C2, #C2, #C2, #C2, #C2, #C2, #C2, #C2, #C3, #C3, #C3, #C3, #C2, #C2, #C2, #C2, #C2, #C2, #C2]
+  #C5 = ffi::_FfiAbiSpecificMapping {nativeTypes:#C4}
+  #C6 = core::pragma {name:#C1, options:#C5}
+  #C7 = "vm:prefer-inline"
+  #C8 = core::pragma {name:#C7, options:#C2}
+  #C9 = 4
+  #C10 = <core::int*>[#C2, #C2, #C2, #C2, #C2, #C2, #C2, #C2, #C2, #C9, #C9, #C9, #C9, #C2, #C2, #C2, #C2, #C2, #C2, #C2]
+  #C11 = "vm:ffi:struct-fields"
+  #C12 = TypeLiteralConstant(self::Incomplete)
+  #C13 = <core::Type>[#C12, #C12]
+  #C14 = ffi::_FfiStructLayout {fieldTypes:#C13, packing:#C2}
+  #C15 = core::pragma {name:#C11, options:#C14}
+  #C16 = 0
+  #C17 = <core::int*>[#C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16, #C16]
+  #C18 = 8
+  #C19 = <core::int*>[#C2, #C2, #C2, #C2, #C2, #C2, #C2, #C2, #C2, #C18, #C18, #C18, #C18, #C2, #C2, #C2, #C2, #C2, #C2, #C2]
+  #C20 = 100
+  #C21 = ffi::_FfiInlineArray {elementType:#C12, length:#C20}
+  #C22 = <core::Type>[#C21]
+  #C23 = ffi::_FfiStructLayout {fieldTypes:#C22, packing:#C2}
+  #C24 = core::pragma {name:#C11, options:#C23}
+  #C25 = <core::int*>[]
+  #C26 = 400
+  #C27 = <core::int*>[#C2, #C2, #C2, #C2, #C2, #C2, #C2, #C2, #C2, #C26, #C26, #C26, #C26, #C2, #C2, #C2, #C2, #C2, #C2, #C2]
+  #C28 = self::_DummyAllocator {}
+}
diff --git a/pkg/vm/testcases/transformations/ffi/compound_copies.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/compound_copies.dart.aot.expect
new file mode 100644
index 0000000..e4dd62a
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/compound_copies.dart.aot.expect
@@ -0,0 +1,6 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+
+import "dart:ffi";
+
+static method main() → void {}
diff --git a/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect
new file mode 100644
index 0000000..b4e558d
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/ffinative.dart.aot.expect
@@ -0,0 +1,121 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:nativewrappers" as nat;
+import "dart:_internal" as _in;
+
+import "dart:ffi";
+import "dart:nativewrappers";
+
+abstract class Classy extends core::Object {
+[@vm.inferred-type.metadata=dart.core::_Closure?]  static final field (core::int) → core::int _returnIntPtrStatic$FfiNative$Ptr = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(core::int) → core::int, (ffi::IntPtr*) →* ffi::IntPtr*>([@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::_fromAddress<ffi::NativeFunction<(ffi::IntPtr*) →* ffi::IntPtr*>*>(ffi::_ffi_resolver(#C1, #C2, #C3){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+  static method returnIntPtrStatic() → core::int
+    return self::Classy::_returnIntPtrStatic$FfiNative$Ptr(#C4){(core::int) → core::int};
+}
+class NativeClassy extends nat::NativeFieldWrapperClass1 {
+[@vm.inferred-type.metadata=dart.core::_Closure?]  static final field (ffi::Pointer<ffi::Void>, core::int) → void _goodHasReceiverPointer$FfiNative$Ptr = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(ffi::Pointer<ffi::Void>, core::int) → void, (ffi::Pointer<ffi::Void*>*, ffi::IntPtr*) →* ffi::Void*>([@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::_fromAddress<ffi::NativeFunction<(ffi::Pointer<ffi::Void*>*, ffi::IntPtr*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C5, #C6){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+[@vm.inferred-type.metadata=dart.core::_Closure?]  static final field (self::NativeClassy, core::int) → void _goodHasReceiverHandle$FfiNative$Ptr = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(self::NativeClassy, core::int) → void, (ffi::Handle*, ffi::IntPtr*) →* ffi::Void*>([@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::_fromAddress<ffi::NativeFunction<(ffi::Handle*, ffi::IntPtr*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C5, #C6){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+[@vm.inferred-type.metadata=dart.core::_Closure?]  static final field (self::NativeClassy, ffi::Pointer<ffi::Void>) → void _goodHasReceiverHandleAndPtr$FfiNative$Ptr = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(self::NativeClassy, ffi::Pointer<ffi::Void>) → void, (ffi::Handle*, ffi::Pointer<ffi::Void*>*) →* ffi::Void*>([@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::_fromAddress<ffi::NativeFunction<(ffi::Handle*, ffi::Pointer<ffi::Void*>*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C5, #C6){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+[@vm.inferred-type.metadata=dart.core::_Closure?]  static final field (self::NativeClassy, self::NativeClassy) → void _goodHasReceiverHandleAndHandle$FfiNative$Ptr = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(self::NativeClassy, self::NativeClassy) → void, (ffi::Handle*, ffi::Handle*) →* ffi::Void*>([@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::_fromAddress<ffi::NativeFunction<(ffi::Handle*, ffi::Handle*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C5, #C6){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+[@vm.inferred-type.metadata=dart.core::_Closure?]  static final field (ffi::Pointer<ffi::Void>, self::NativeClassy) → void _goodHasReceiverPtrAndHandle$FfiNative$Ptr = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(ffi::Pointer<ffi::Void>, self::NativeClassy) → void, (ffi::Pointer<ffi::Void*>*, ffi::Handle*) →* ffi::Void*>([@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::_fromAddress<ffi::NativeFunction<(ffi::Pointer<ffi::Void*>*, ffi::Handle*) →* ffi::Void*>*>(ffi::_ffi_resolver(#C1, #C5, #C6){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+[@vm.inferred-type.metadata=dart.core::_Closure?]  static final field (ffi::Pointer<ffi::Void>, core::bool) → core::Object? _meh$FfiNative$Ptr = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(ffi::Pointer<ffi::Void>, core::bool) → core::Object?, (ffi::Pointer<ffi::Void*>*, ffi::Bool*) →* ffi::Handle*>([@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::_fromAddress<ffi::NativeFunction<(ffi::Pointer<ffi::Void*>*, ffi::Bool*) →* ffi::Handle*>*>(ffi::_ffi_resolver(#C1, #C5, #C6){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+[@vm.inferred-type.metadata=dart.core::_Closure?]  static final field (ffi::Pointer<ffi::Void>) → core::bool _blah$FfiNative$Ptr = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(ffi::Pointer<ffi::Void>) → core::bool, (ffi::Pointer<ffi::Void*>*) →* ffi::Bool*>([@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::_fromAddress<ffi::NativeFunction<(ffi::Pointer<ffi::Void*>*) →* ffi::Bool*>*>(ffi::_ffi_resolver(#C1, #C5, #C3){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+  synthetic constructor •() → self::NativeClassy
+    : super nat::NativeFieldWrapperClass1::•()
+    ;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2]  method goodHasReceiverPointer() → void
+    return block {
+      final nat::NativeFieldWrapperClass1 #t1 = this;
+      final core::int #t2 = #C7;
+      final void #t3 = self::NativeClassy::_goodHasReceiverPointer$FfiNative$Ptr(ffi::_fromAddress<ffi::Void>( block {
+        core::int #pointerAddress = [@vm.inferred-type.metadata=int?] nat::_getNativeField(#t1);
+        if([@vm.inferred-type.metadata=dart.core::bool] #pointerAddress.{core::Object::==}(#C8){(core::Object) → core::bool})
+          core::StateError::_throwNew(#C9);
+        else
+          ;
+      } =>#pointerAddress), #t2){(ffi::Pointer<ffi::Void>, core::int) → void};
+      _in::reachabilityFence(#t1);
+    } =>#t3;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3,getterSelectorId:4]  method goodHasReceiverHandle() → void
+    return self::NativeClassy::_goodHasReceiverHandle$FfiNative$Ptr(this, #C7){(self::NativeClassy, core::int) → void};
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:5,getterSelectorId:6]  method goodHasReceiverHandleAndPtr([@vm.inferred-type.metadata=#lib::NativeClassy] self::NativeClassy v) → void
+    return block {
+      final self::NativeClassy #t4 = this;
+      final nat::NativeFieldWrapperClass1 #t5 = v;
+      final void #t6 = self::NativeClassy::_goodHasReceiverHandleAndPtr$FfiNative$Ptr(#t4, ffi::_fromAddress<ffi::Void>([@vm.inferred-type.metadata=int?] nat::_getNativeField(#t5))){(self::NativeClassy, ffi::Pointer<ffi::Void>) → void};
+      _in::reachabilityFence(#t5);
+    } =>#t6;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:7,getterSelectorId:8]  method goodHasReceiverHandleAndHandle([@vm.inferred-type.metadata=#lib::NativeClassy] self::NativeClassy v) → void
+    return self::NativeClassy::_goodHasReceiverHandleAndHandle$FfiNative$Ptr(this, v){(self::NativeClassy, self::NativeClassy) → void};
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:9,getterSelectorId:10]  method goodHasReceiverPtrAndHandle([@vm.inferred-type.metadata=#lib::NativeClassy] self::NativeClassy v) → void
+    return block {
+      final nat::NativeFieldWrapperClass1 #t7 = this;
+      final self::NativeClassy #t8 = v;
+      final void #t9 = self::NativeClassy::_goodHasReceiverPtrAndHandle$FfiNative$Ptr(ffi::_fromAddress<ffi::Void>( block {
+        core::int #pointerAddress = [@vm.inferred-type.metadata=int?] nat::_getNativeField(#t7);
+        if([@vm.inferred-type.metadata=dart.core::bool] #pointerAddress.{core::Object::==}(#C8){(core::Object) → core::bool})
+          core::StateError::_throwNew(#C9);
+        else
+          ;
+      } =>#pointerAddress), #t8){(ffi::Pointer<ffi::Void>, self::NativeClassy) → void};
+      _in::reachabilityFence(#t7);
+    } =>#t9;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:11,getterSelectorId:12]  method meh() → core::String?
+    return block {
+      final nat::NativeFieldWrapperClass1 #t10 = this;
+      final core::bool #t11 = #C10;
+      final core::String? #t12 = _in::unsafeCast<core::String?>(self::NativeClassy::_meh$FfiNative$Ptr(ffi::_fromAddress<ffi::Void>( block {
+        core::int #pointerAddress = [@vm.inferred-type.metadata=int?] nat::_getNativeField(#t10);
+        if([@vm.inferred-type.metadata=dart.core::bool] #pointerAddress.{core::Object::==}(#C8){(core::Object) → core::bool})
+          core::StateError::_throwNew(#C9);
+        else
+          ;
+      } =>#pointerAddress), #t11){(ffi::Pointer<ffi::Void>, core::bool) → core::Object?});
+      _in::reachabilityFence(#t10);
+    } =>#t12;
+[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:13,getterSelectorId:14]  method blah() → core::bool
+    return block {
+      final nat::NativeFieldWrapperClass1 #t13 = this;
+      final core::bool #t14 = self::NativeClassy::_blah$FfiNative$Ptr(ffi::_fromAddress<ffi::Void>( block {
+        core::int #pointerAddress = [@vm.inferred-type.metadata=int?] nat::_getNativeField(#t13);
+        if([@vm.inferred-type.metadata=dart.core::bool] #pointerAddress.{core::Object::==}(#C8){(core::Object) → core::bool})
+          core::StateError::_throwNew(#C9);
+        else
+          ;
+      } =>#pointerAddress)){(ffi::Pointer<ffi::Void>) → core::bool};
+      _in::reachabilityFence(#t13);
+    } =>#t14;
+}
+[@vm.inferred-type.metadata=dart.core::_Closure?]static final field (core::int) → core::int _returnIntPtr$FfiNative$Ptr = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(core::int) → core::int, (ffi::IntPtr*) →* ffi::IntPtr*>([@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::_fromAddress<ffi::NativeFunction<(ffi::IntPtr*) →* ffi::IntPtr*>*>(ffi::_ffi_resolver(#C1, #C2, #C3){(core::Object, core::Object, core::int) → core::int}), false)/*isLegacy*/;
+[@vm.inferred-type.metadata=dart.core::_Closure?]static final field (core::int) → core::int _returnIntPtrLeaf$FfiNative$Ptr = [@vm.inferred-type.metadata=dart.core::_Closure?] ffi::_asFunctionInternal<(core::int) → core::int, (ffi::IntPtr*) →* ffi::IntPtr*>([@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::_fromAddress<ffi::NativeFunction<(ffi::IntPtr*) →* ffi::IntPtr*>*>(ffi::_ffi_resolver(#C1, #C2, #C3){(core::Object, core::Object, core::int) → core::int}), true)/*isLegacy*/;
+static method returnIntPtr() → core::int
+  return self::_returnIntPtr$FfiNative$Ptr(#C11){(core::int) → core::int};
+static method returnIntPtrLeaf() → core::int
+  return self::_returnIntPtrLeaf$FfiNative$Ptr(#C12){(core::int) → core::int};
+static method main() → void {
+  self::returnIntPtr();
+  self::returnIntPtrLeaf();
+  self::Classy::returnIntPtrStatic();
+  [@vm.direct-call.metadata=#lib::NativeClassy.goodHasReceiverPointer] [@vm.inferred-type.metadata=!? (skip check)] new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverPointer}(){(core::int) → void};
+  [@vm.direct-call.metadata=#lib::NativeClassy.goodHasReceiverHandle] [@vm.inferred-type.metadata=!? (skip check)] new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverHandle}(){(core::int) → void};
+  [@vm.direct-call.metadata=#lib::NativeClassy.goodHasReceiverHandleAndPtr] [@vm.inferred-type.metadata=!? (skip check)] new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverHandleAndPtr}(new self::NativeClassy::•()){(self::NativeClassy) → void};
+  [@vm.direct-call.metadata=#lib::NativeClassy.goodHasReceiverHandleAndHandle] [@vm.inferred-type.metadata=!? (skip check)] new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverHandleAndHandle}(new self::NativeClassy::•()){(self::NativeClassy) → void};
+  [@vm.direct-call.metadata=#lib::NativeClassy.goodHasReceiverPtrAndHandle] [@vm.inferred-type.metadata=!? (skip check)] new self::NativeClassy::•().{self::NativeClassy::goodHasReceiverPtrAndHandle}(new self::NativeClassy::•()){(self::NativeClassy) → void};
+  [@vm.direct-call.metadata=#lib::NativeClassy.meh] [@vm.inferred-type.metadata=!? (skip check)] new self::NativeClassy::•().{self::NativeClassy::meh}(){(core::bool) → core::String?};
+  [@vm.direct-call.metadata=#lib::NativeClassy.blah] [@vm.inferred-type.metadata=!? (skip check)] new self::NativeClassy::•().{self::NativeClassy::blah}(){() → core::bool};
+}
+constants  {
+  #C1 = "#lib"
+  #C2 = "ReturnIntPtr"
+  #C3 = 1
+  #C4 = 222
+  #C5 = "doesntmatter"
+  #C6 = 2
+  #C7 = 175
+  #C8 = 0
+  #C9 = "Native field is nullptr."
+  #C10 = true
+  #C11 = 13
+  #C12 = 37
+}
diff --git a/pkg/vm/testcases/transformations/ffi/finalizable_async.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/finalizable_async.dart.aot.expect
new file mode 100644
index 0000000..0324cf7
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/finalizable_async.dart.aot.expect
@@ -0,0 +1,50 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:async" as asy;
+import "dart:_internal" as _in;
+
+import "dart:ffi";
+
+class MyFinalizable extends core::Object implements ffi::Finalizable {
+  synthetic constructor •() → self::MyFinalizable
+    : super core::Object::•()
+    ;
+}
+static method doSomething() → asy::Future<core::int> async /* futureValueType= core::int */ 
+  return 3;
+static method useFinalizableAsync([@vm.inferred-type.metadata=#lib::MyFinalizable] ffi::Finalizable finalizable) → asy::Future<core::int> async /* futureValueType= core::int */ {
+  await block {
+    final asy::Future<core::int> :expressionValueWrappedFinalizable = asy::Future::sync<core::int>(() → core::int => 6);
+    _in::reachabilityFence(finalizable);
+  } =>:expressionValueWrappedFinalizable;
+  final self::MyFinalizable finalizable2 = new self::MyFinalizable::•();
+  await block {
+    final asy::Future<core::int> :expressionValueWrappedFinalizable = asy::Future::sync<core::int>(() → core::int => 5);
+    _in::reachabilityFence(finalizable);
+    _in::reachabilityFence(finalizable2);
+  } =>:expressionValueWrappedFinalizable;
+  final self::MyFinalizable finalizable3 = new self::MyFinalizable::•();
+  await block {
+    final asy::Future<core::int> :expressionValueWrappedFinalizable = asy::Future::sync<core::int>(() → core::int => 4);
+    _in::reachabilityFence(finalizable);
+    _in::reachabilityFence(finalizable2);
+    _in::reachabilityFence(finalizable3);
+  } =>:expressionValueWrappedFinalizable;
+  return block {
+    final asy::Future<core::int> :expressionValueWrappedFinalizable = self::doSomething();
+    _in::reachabilityFence(finalizable);
+    _in::reachabilityFence(finalizable2);
+    _in::reachabilityFence(finalizable3);
+  } =>:expressionValueWrappedFinalizable;
+}
+static method main() → void async /* futureValueType= void */ {
+  final self::MyFinalizable finalizable = new self::MyFinalizable::•();
+  final asy::Future<core::int> asyncResult = self::useFinalizableAsync(finalizable);
+  core::print(await block {
+    final asy::Future<core::int> :expressionValueWrappedFinalizable = asyncResult;
+    _in::reachabilityFence(finalizable);
+  } =>:expressionValueWrappedFinalizable);
+  _in::reachabilityFence(finalizable);
+}
diff --git a/pkg/vm/testcases/transformations/ffi/finalizable_async_star.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/finalizable_async_star.dart.aot.expect
new file mode 100644
index 0000000..81e23f1
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/finalizable_async_star.dart.aot.expect
@@ -0,0 +1,71 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:async" as asy;
+import "dart:_internal" as _in;
+
+import "dart:ffi";
+
+class MyFinalizable extends core::Object implements ffi::Finalizable {
+  synthetic constructor •() → self::MyFinalizable
+    : super core::Object::•()
+    ;
+}
+[@vm.unboxing-info.metadata=()->i]static method doSomething() → core::int
+  return 3;
+static method useFinalizableAsyncStar([@vm.inferred-type.metadata=#lib::MyFinalizable] ffi::Finalizable finalizable) → asy::Stream<core::int> async* {
+  final self::MyFinalizable finalizable2 = new self::MyFinalizable::•();
+  yield block {
+    final core::int :expressionValueWrappedFinalizable = self::doSomething();
+    _in::reachabilityFence(finalizable);
+    _in::reachabilityFence(finalizable2);
+  } =>:expressionValueWrappedFinalizable;
+  final self::MyFinalizable finalizable3 = new self::MyFinalizable::•();
+  await block {
+    final asy::Future<core::int> :expressionValueWrappedFinalizable = asy::Future::sync<core::int>(() → core::int => 3);
+    _in::reachabilityFence(finalizable);
+    _in::reachabilityFence(finalizable2);
+    _in::reachabilityFence(finalizable3);
+  } =>:expressionValueWrappedFinalizable;
+  final self::MyFinalizable finalizable4 = new self::MyFinalizable::•();
+  if([@vm.inferred-type.metadata=dart.core::bool] [@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] [@vm.inferred-type.metadata=int] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int} =={core::num::==}{(core::Object) → core::bool} 4) {
+    {
+      _in::reachabilityFence(finalizable);
+      _in::reachabilityFence(finalizable2);
+      _in::reachabilityFence(finalizable3);
+      _in::reachabilityFence(finalizable4);
+      return;
+    }
+  }
+  yield block {
+    final core::int :expressionValueWrappedFinalizable = 5;
+    _in::reachabilityFence(finalizable);
+    _in::reachabilityFence(finalizable2);
+    _in::reachabilityFence(finalizable3);
+    _in::reachabilityFence(finalizable4);
+  } =>:expressionValueWrappedFinalizable;
+  _in::reachabilityFence(finalizable2);
+  _in::reachabilityFence(finalizable3);
+  _in::reachabilityFence(finalizable4);
+  _in::reachabilityFence(finalizable);
+}
+static method main() → void async /* futureValueType= void */ {
+  final self::MyFinalizable finalizable = new self::MyFinalizable::•();
+  final asy::Stream<core::int> asyncStarResult = [@vm.inferred-type.metadata=!] self::useFinalizableAsyncStar(finalizable);
+  {
+    asy::Stream<core::int> :stream = asyncStarResult;
+    asy::_StreamIterator<core::int>? :for-iterator = new asy::_StreamIterator::•<core::int>(:stream);
+    try
+      while (let dynamic #t1 = asy::_asyncStarMoveNextHelper(:stream) in await [@vm.direct-call.metadata=dart.async::_StreamIterator.moveNext] [@vm.inferred-type.metadata=!? (skip check)] :for-iterator.{asy::_StreamIterator::moveNext}(){() → asy::Future<core::bool>}) {
+        final core::int element = [@vm.direct-call.metadata=dart.async::_StreamIterator.current] [@vm.inferred-type.metadata=int?] :for-iterator.{asy::_StreamIterator::current}{core::int};
+        {
+          core::print(element);
+        }
+      }
+    finally
+      if(!([@vm.direct-call.metadata=dart.async::_StreamIterator._subscription] :for-iterator.{asy::_StreamIterator::_subscription}{asy::StreamSubscription<core::int>?} == null))
+        await [@vm.direct-call.metadata=dart.async::_StreamIterator.cancel] [@vm.inferred-type.metadata=!? (skip check)] :for-iterator.{asy::_StreamIterator::cancel}(){() → asy::Future<dynamic>};
+  }
+  _in::reachabilityFence(finalizable);
+}
diff --git a/pkg/vm/testcases/transformations/ffi/finalizable_extension_method.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/finalizable_extension_method.dart.aot.expect
new file mode 100644
index 0000000..4c37d02
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/finalizable_extension_method.dart.aot.expect
@@ -0,0 +1,39 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:_internal" as _in;
+
+import "dart:ffi";
+
+class Foo extends core::Object implements ffi::Finalizable {
+  synthetic constructor •() → self::Foo
+    : super core::Object::•()
+    ;
+}
+extension _extension#0 on ffi::Finalizable {
+  method bar = self::_extension#0|bar;
+}
+extension _extension#1 on core::Object {
+  method baz = self::_extension#1|baz;
+}
+static method main() → void {
+  final self::Foo foo = new self::Foo::•();
+  self::_extension#0|bar(foo);
+  let final core::Object #t1 = new core::Object::•() in self::_extension#1|baz(foo);
+  _in::reachabilityFence(foo);
+}
+[@vm.unboxing-info.metadata=(b)->i]static method _extension#0|bar([@vm.inferred-type.metadata=#lib::Foo] lowered final ffi::Finalizable #this) → core::int {
+  core::print("123");
+  return block {
+    final core::int :expressionValueWrappedFinalizable = 4;
+    _in::reachabilityFence(#this);
+  } =>:expressionValueWrappedFinalizable;
+}
+[@vm.unboxing-info.metadata=(b)->i]static method _extension#1|baz([@vm.inferred-type.metadata=#lib::Foo] self::Foo foo) → core::int {
+  core::print("456");
+  return block {
+    final core::int :expressionValueWrappedFinalizable = 5;
+    _in::reachabilityFence(foo);
+  } =>:expressionValueWrappedFinalizable;
+}
diff --git a/pkg/vm/testcases/transformations/ffi/finalizable_late.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/finalizable_late.dart.aot.expect
new file mode 100644
index 0000000..45321de
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/finalizable_late.dart.aot.expect
@@ -0,0 +1,22 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:_internal" as _in;
+
+import "dart:ffi";
+
+class Foo extends core::Object implements ffi::Finalizable {
+  synthetic constructor •() → self::Foo
+    : super core::Object::•()
+    ;
+}
+static method main() → void {
+  late self::Foo foo;
+  foo = block {
+    final self::Foo :expressionValueWrappedFinalizable = new self::Foo::•();
+    _in::reachabilityFence(foo);
+  } =>:expressionValueWrappedFinalizable;
+  core::print(foo);
+  _in::reachabilityFence(foo);
+}
diff --git a/pkg/vm/testcases/transformations/ffi/finalizable_member.dart b/pkg/vm/testcases/transformations/ffi/finalizable_member.dart
new file mode 100644
index 0000000..ce5e063
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/finalizable_member.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2022, 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.16
+
+import 'dart:ffi';
+import 'dart:io';
+
+typedef Free = NativeFunction<Void Function(Pointer)>;
+final free = DynamicLibrary.process().lookup<Free>('free');
+
+final _nativeFinalizer = NativeFinalizer(free);
+
+class A implements Finalizable {
+  A() {
+    _nativeFinalizer.attach(this, Pointer.fromAddress(1),
+        detach: this, externalSize: 1 << 32); // will crash, if it ever runs
+  }
+}
+
+class B implements Finalizable {
+  final A a;
+
+  B(this.a);
+}
+
+Future<void> main() async {
+  // ignore: unused_local_variable
+  final b = B(A()); // I would expect b.a to live as long as b
+  final l = <int>[];
+  Future.doWhile(() {
+    l.add(1); // put some pressure on GC
+    return true;
+  });
+  await ProcessSignal.sigint.watch().first;
+  // b still alive here, but what about b.a?
+}
diff --git a/pkg/vm/testcases/transformations/ffi/finalizable_member.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/finalizable_member.dart.aot.expect
new file mode 100644
index 0000000..aa60815
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/finalizable_member.dart.aot.expect
@@ -0,0 +1,47 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:_internal" as _in;
+import "dart:async" as asy;
+import "dart:io" as io;
+
+import "dart:ffi";
+import "dart:io";
+
+class A extends core::Object implements ffi::Finalizable {
+  constructor •() → self::A
+    : super core::Object::•() {
+    let final ffi::NativeFinalizer #t1 = [@vm.inferred-type.metadata=dart.ffi::_NativeFinalizer?] self::_nativeFinalizer in let final ffi::Pointer<ffi::Void> #t2 = [@vm.inferred-type.metadata=dart.ffi::Pointer?] ffi::Pointer::fromAddress<ffi::Void>(1) in let final core::int #t3 = [@vm.direct-call.metadata=dart.core::_IntegerImplementation.<<] [@vm.inferred-type.metadata=int (skip check)] 1.{core::int::<<}(32){(core::int) → core::int} in [@vm.direct-call.metadata=dart.ffi::_NativeFinalizer.attach??] [@vm.inferred-type.metadata=!? (skip check)] #t1.{ffi::NativeFinalizer::attach}(this, #t2, this, #t3){(ffi::Finalizable, ffi::Pointer<ffi::Void>, {detach: core::Object?, externalSize: core::int?}) → void};
+    _in::reachabilityFence(this);
+  }
+}
+class B extends core::Object implements ffi::Finalizable {
+[@vm.inferred-type.metadata=#lib::A] [@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasNonThisUses:false,hasTearOffUses:false,getterSelectorId:1]  final field self::A a;
+  constructor •([@vm.inferred-type.metadata=#lib::A] self::A a) → self::B
+    : self::B::a = a, super core::Object::•() {
+    ;
+    _in::reachabilityFence(this);
+    _in::reachabilityFence(a);
+  }
+}
+[@vm.inferred-type.metadata=dart.ffi::Pointer?]static final field ffi::Pointer<ffi::NativeFunction<(ffi::Pointer<ffi::NativeType>) → ffi::Void>> free = [@vm.direct-call.metadata=dart.ffi::DynamicLibrary.lookup??] [@vm.inferred-type.metadata=dart.ffi::Pointer? (skip check)] [@vm.inferred-type.metadata=dart.ffi::DynamicLibrary?] ffi::DynamicLibrary::process().{ffi::DynamicLibrary::lookup}<ffi::NativeFunction<(ffi::Pointer<ffi::NativeType>) → ffi::Void>>("free"){(core::String) → ffi::Pointer<ffi::NativeFunction<(ffi::Pointer<ffi::NativeType>) → ffi::Void>>};
+[@vm.inferred-type.metadata=dart.ffi::_NativeFinalizer?]static final field ffi::NativeFinalizer _nativeFinalizer = new ffi::_NativeFinalizer::•([@vm.inferred-type.metadata=dart.ffi::Pointer?] self::free);
+static method main() → asy::Future<void> async /* futureValueType= void */ {
+  final self::B b = new self::B::•(new self::A::•());
+  final core::List<core::int> l = [@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::int>] core::_GrowableList::•<core::int>(0);
+  asy::Future::doWhile(() → core::bool {
+    [@vm.call-site-attributes.metadata=receiverType:dart.core::List<dart.core::int>] [@vm.direct-call.metadata=dart.core::_GrowableList.add] [@vm.inferred-type.metadata=!? (skip check)] l.{core::List::add}(1){(core::int) → void};
+    return true;
+  });
+  await block {
+    final asy::Future<io::ProcessSignal> :expressionValueWrappedFinalizable = [@vm.direct-call.metadata=dart.async::Stream.first] [@vm.direct-call.metadata=dart.io::ProcessSignal.watch] [@vm.inferred-type.metadata=dart.async::_BroadcastStream<dart.io::ProcessSignal> (skip check)] #C3.{io::ProcessSignal::watch}(){() → asy::Stream<io::ProcessSignal>}.{asy::Stream::first}{asy::Future<io::ProcessSignal>};
+    _in::reachabilityFence(b);
+  } =>:expressionValueWrappedFinalizable;
+  _in::reachabilityFence(b);
+}
+constants  {
+  #C1 = 2
+  #C2 = "SIGINT"
+  #C3 = io::ProcessSignal {_signalNumber:#C1, _name:#C2}
+}
diff --git a/pkg/vm/testcases/transformations/ffi/finalizable_member.dart.expect b/pkg/vm/testcases/transformations/ffi/finalizable_member.dart.expect
new file mode 100644
index 0000000..c702f9d
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/finalizable_member.dart.expect
@@ -0,0 +1,48 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:ffi" as ffi;
+import "dart:core" as core;
+import "dart:_internal" as _in;
+import "dart:async" as asy;
+import "dart:io" as io;
+
+import "dart:ffi";
+import "dart:io";
+
+typedef Free = ffi::NativeFunction<(ffi::Pointer<ffi::NativeType>) → ffi::Void>;
+class A extends core::Object implements ffi::Finalizable {
+  constructor •() → self::A
+    : super core::Object::•() {
+    self::_nativeFinalizer.{ffi::NativeFinalizer::attach}(this, ffi::Pointer::fromAddress<ffi::Void>(1), detach: this, externalSize: 1.{core::int::<<}(32){(core::int) → core::int}){(ffi::Finalizable, ffi::Pointer<ffi::Void>, {detach: core::Object?, externalSize: core::int?}) → void};
+    _in::reachabilityFence(this);
+  }
+}
+class B extends core::Object implements ffi::Finalizable {
+  final field self::A a;
+  constructor •(self::A a) → self::B
+    : self::B::a = a, super core::Object::•() {
+    ;
+    _in::reachabilityFence(this);
+    _in::reachabilityFence(a);
+  }
+}
+static final field ffi::Pointer<ffi::NativeFunction<(ffi::Pointer<ffi::NativeType>) → ffi::Void>> free = ffi::DynamicLibrary::process().{ffi::DynamicLibrary::lookup}<ffi::NativeFunction<(ffi::Pointer<ffi::NativeType>) → ffi::Void>>("free"){(core::String) → ffi::Pointer<ffi::NativeFunction<(ffi::Pointer<ffi::NativeType>) → ffi::Void>>};
+static final field ffi::NativeFinalizer _nativeFinalizer = new ffi::_NativeFinalizer::•(self::free);
+static method main() → asy::Future<void> async /* futureValueType= void */ {
+  final self::B b = new self::B::•(new self::A::•());
+  final core::List<core::int> l = core::_GrowableList::•<core::int>(0);
+  asy::Future::doWhile(() → core::bool {
+    [@vm.call-site-attributes.metadata=receiverType:dart.core::List<dart.core::int>] l.{core::List::add}(1){(core::int) → void};
+    return true;
+  });
+  await block {
+    final asy::Future<io::ProcessSignal> :expressionValueWrappedFinalizable = #C3.{io::ProcessSignal::watch}(){() → asy::Stream<io::ProcessSignal>}.{asy::Stream::first}{asy::Future<io::ProcessSignal>};
+    _in::reachabilityFence(b);
+  } =>:expressionValueWrappedFinalizable;
+  _in::reachabilityFence(b);
+}
+constants  {
+  #C1 = 2
+  #C2 = "SIGINT"
+  #C3 = io::ProcessSignal {_signalNumber:#C1, _name:#C2}
+}
diff --git a/pkg/vm/testcases/transformations/ffi/finalizable_sync.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/finalizable_sync.dart.aot.expect
new file mode 100644
index 0000000..c7152ec
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/finalizable_sync.dart.aot.expect
@@ -0,0 +1,26 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:_internal" as _in;
+
+import "dart:ffi";
+
+class MyFinalizable extends core::Object implements ffi::Finalizable {
+  synthetic constructor •() → self::MyFinalizable
+    : super core::Object::•()
+    ;
+}
+[@vm.unboxing-info.metadata=()->i]static method doSomething() → core::int
+  return 3;
+[@vm.unboxing-info.metadata=(b)->i]static method useFinalizableSync([@vm.inferred-type.metadata=#lib::MyFinalizable] ffi::Finalizable finalizable) → core::int {
+  return block {
+    final core::int :expressionValueWrappedFinalizable = [@vm.inferred-type.metadata=dart.core::_Smi (value: 3)] self::doSomething();
+    _in::reachabilityFence(finalizable);
+  } =>:expressionValueWrappedFinalizable;
+}
+static method main() → void {
+  final self::MyFinalizable finalizable = new self::MyFinalizable::•();
+  core::print([@vm.inferred-type.metadata=dart.core::_Smi (value: 3)] self::useFinalizableSync(finalizable));
+  _in::reachabilityFence(finalizable);
+}
diff --git a/pkg/vm/testcases/transformations/ffi/finalizable_sync2.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/finalizable_sync2.dart.aot.expect
new file mode 100644
index 0000000..27fc81f
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/finalizable_sync2.dart.aot.expect
@@ -0,0 +1,233 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:_internal" as _in;
+
+import "dart:ffi";
+
+class MyFinalizable extends core::Object implements ffi::Finalizable {
+  synthetic constructor •() → self::MyFinalizable
+    : super core::Object::•()
+    ;
+}
+static method main() → void {
+  final self::MyFinalizable finalizable = new self::MyFinalizable::•();
+  {
+    final self::MyFinalizable finalizable2 = new self::MyFinalizable::•();
+    _in::reachabilityFence(finalizable2);
+  }
+  if([@vm.inferred-type.metadata=dart.core::bool] [@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] [@vm.inferred-type.metadata=int] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int} =={core::num::==}{(core::Object) → core::bool} 42) {
+    {
+      _in::reachabilityFence(finalizable);
+      return;
+    }
+  }
+  else {
+    try
+      try {
+        final self::MyFinalizable finalizable3 = new self::MyFinalizable::•();
+        {}
+        _in::reachabilityFence(finalizable3);
+      }
+      on core::Exception catch(no-exception-var) {
+        final self::MyFinalizable finalizable4 = new self::MyFinalizable::•();
+        _in::reachabilityFence(finalizable4);
+      }
+    finally {
+      final self::MyFinalizable finalizable5 = new self::MyFinalizable::•();
+      _in::reachabilityFence(finalizable5);
+    }
+    try {
+      final self::MyFinalizable finalizable13 = new self::MyFinalizable::•();
+      try
+        try {
+          final self::MyFinalizable finalizable14 = new self::MyFinalizable::•();
+          if([@vm.inferred-type.metadata=dart.core::bool] [@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] [@vm.inferred-type.metadata=int] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int} =={core::num::==}{(core::Object) → core::bool} 100) {
+            throw block {
+              final core::Exception :expressionValueWrappedFinalizable = core::Exception::•("foo");
+              _in::reachabilityFence(finalizable14);
+            } =>:expressionValueWrappedFinalizable;
+          }
+          if([@vm.inferred-type.metadata=dart.core::bool] [@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] [@vm.inferred-type.metadata=int] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int} =={core::num::==}{(core::Object) → core::bool} 101) {
+            throw block {
+              final core::Error :expressionValueWrappedFinalizable = new core::Error::•();
+              _in::reachabilityFence(finalizable);
+              _in::reachabilityFence(finalizable13);
+              _in::reachabilityFence(finalizable14);
+            } =>:expressionValueWrappedFinalizable;
+          }
+          _in::reachabilityFence(finalizable14);
+        }
+        on core::Exception catch(final core::Exception e) {
+          core::print(e);
+          block {
+            _in::reachabilityFence(finalizable13);
+          } =>rethrow;
+        }
+      finally {
+        if([@vm.inferred-type.metadata=dart.core::bool] [@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] [@vm.inferred-type.metadata=int] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int} =={core::num::==}{(core::Object) → core::bool} 1000) {
+          throw block {
+            final core::Exception :expressionValueWrappedFinalizable = core::Exception::•("bar");
+            _in::reachabilityFence(finalizable13);
+          } =>:expressionValueWrappedFinalizable;
+        }
+      }
+      _in::reachabilityFence(finalizable13);
+    }
+    on core::Exception catch(final core::Exception e) {
+      core::print(e);
+    }
+  }
+  #L1:
+  switch([@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int}) {
+    #L2:
+    case #C1:
+    case #C2:
+      {
+        final self::MyFinalizable finalizable6 = new self::MyFinalizable::•();
+        {
+          _in::reachabilityFence(finalizable);
+          _in::reachabilityFence(finalizable6);
+          return;
+        }
+      }
+    #L3:
+    case #C3:
+      {
+        final self::MyFinalizable finalizable7 = new self::MyFinalizable::•();
+        {
+          _in::reachabilityFence(finalizable7);
+          break #L1;
+        }
+      }
+    #L4:
+    case #C4:
+      {
+        final self::MyFinalizable finalizable70 = new self::MyFinalizable::•();
+        #L5:
+        switch([@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int}) {
+          #L6:
+          case #C5:
+            {
+              final self::MyFinalizable finalizable71 = new self::MyFinalizable::•();
+              if([@vm.inferred-type.metadata=dart.core::bool] [@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] [@vm.inferred-type.metadata=int] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int} =={core::num::==}{(core::Object) → core::bool} 44) {
+                {
+                  _in::reachabilityFence(finalizable70);
+                  _in::reachabilityFence(finalizable71);
+                  continue #L4;
+                }
+              }
+              {
+                _in::reachabilityFence(finalizable71);
+                break #L5;
+              }
+            }
+        }
+        {
+          _in::reachabilityFence(finalizable70);
+          continue #L3;
+        }
+      }
+    #L7:
+    default:
+      {
+        final self::MyFinalizable finalizable8 = new self::MyFinalizable::•();
+        _in::reachabilityFence(finalizable8);
+      }
+  }
+  #L8:
+  for (core::int i = 0; [@vm.direct-call.metadata=dart.core::_IntegerImplementation.<] [@vm.inferred-type.metadata=dart.core::bool (skip check)] i.{core::num::<}(10){(core::num) → core::bool}; i = [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(1){(core::num) → core::int})
+    #L9:
+    {
+      final self::MyFinalizable finalizable9 = new self::MyFinalizable::•();
+      for (core::int j = 0; [@vm.direct-call.metadata=dart.core::_IntegerImplementation.<] [@vm.inferred-type.metadata=dart.core::bool (skip check)] j.{core::num::<}(10){(core::num) → core::bool}; j = [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] j.{core::num::+}(1){(core::num) → core::int})
+        #L10:
+        {
+          final self::MyFinalizable finalizable10 = new self::MyFinalizable::•();
+          if([@vm.inferred-type.metadata=dart.core::bool] [@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] [@vm.inferred-type.metadata=int] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int} =={core::num::==}{(core::Object) → core::bool} 42) {
+            {
+              _in::reachabilityFence(finalizable9);
+              _in::reachabilityFence(finalizable10);
+              break #L8;
+            }
+          }
+          if([@vm.inferred-type.metadata=dart.core::bool] [@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] [@vm.inferred-type.metadata=int] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int} =={core::num::==}{(core::Object) → core::bool} 1337) {
+            {
+              _in::reachabilityFence(finalizable9);
+              _in::reachabilityFence(finalizable10);
+              break #L8;
+            }
+          }
+          if([@vm.inferred-type.metadata=dart.core::bool] [@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] [@vm.inferred-type.metadata=int] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int} =={core::num::==}{(core::Object) → core::bool} 1) {
+            {
+              _in::reachabilityFence(finalizable10);
+              break #L10;
+            }
+          }
+          if([@vm.inferred-type.metadata=dart.core::bool] [@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] [@vm.inferred-type.metadata=int] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int} =={core::num::==}{(core::Object) → core::bool} 3) {
+            {
+              _in::reachabilityFence(finalizable9);
+              _in::reachabilityFence(finalizable10);
+              break #L9;
+            }
+          }
+          _in::reachabilityFence(finalizable10);
+        }
+      _in::reachabilityFence(finalizable9);
+    }
+  #L11:
+  {
+    final self::MyFinalizable finalizable11 = new self::MyFinalizable::•();
+    #L12:
+    {
+      final self::MyFinalizable finalizable12 = new self::MyFinalizable::•();
+      if([@vm.inferred-type.metadata=dart.core::bool] [@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] [@vm.inferred-type.metadata=int] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int} =={core::num::==}{(core::Object) → core::bool} 1) {
+        {
+          _in::reachabilityFence(finalizable11);
+          _in::reachabilityFence(finalizable12);
+          break #L11;
+        }
+      }
+      if([@vm.inferred-type.metadata=dart.core::bool] [@vm.direct-call.metadata=dart.core::DateTime.millisecondsSinceEpoch] [@vm.inferred-type.metadata=int] new core::DateTime::now().{core::DateTime::millisecondsSinceEpoch}{core::int} =={core::num::==}{(core::Object) → core::bool} 3) {
+        {
+          _in::reachabilityFence(finalizable12);
+          break #L12;
+        }
+      }
+      _in::reachabilityFence(finalizable12);
+    }
+    _in::reachabilityFence(finalizable11);
+  }
+  for (core::int i = 0; [@vm.direct-call.metadata=dart.core::_IntegerImplementation.<] [@vm.inferred-type.metadata=dart.core::bool (skip check)] i.{core::num::<}(10){(core::num) → core::bool}; i = [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(1){(core::num) → core::int}) {
+    final self::MyFinalizable finalizable15 = new self::MyFinalizable::•();
+    _in::reachabilityFence(finalizable15);
+  }
+  core::int i = 0;
+  while ([@vm.direct-call.metadata=dart.core::_IntegerImplementation.<] [@vm.inferred-type.metadata=dart.core::bool (skip check)] i.{core::num::<}(10){(core::num) → core::bool}) {
+    final self::MyFinalizable finalizable16 = new self::MyFinalizable::•();
+    i = [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(1){(core::num) → core::int};
+    _in::reachabilityFence(finalizable16);
+  }
+  {
+    core::Iterator<ffi::Finalizable> :sync-for-iterator = [@vm.inferred-type.metadata=!] [@vm.inferred-type.metadata=!] core::Iterable::generate<ffi::Finalizable>((core::int index) → self::MyFinalizable => new self::MyFinalizable::•()).{core::Iterable::iterator}{core::Iterator<ffi::Finalizable>};
+    for (; [@vm.inferred-type.metadata=dart.core::bool] :sync-for-iterator.{core::Iterator::moveNext}(){() → core::bool}; ) {
+      final ffi::Finalizable finalizable17 = [@vm.inferred-type.metadata=#lib::MyFinalizable?] :sync-for-iterator.{core::Iterator::current}{ffi::Finalizable};
+      {
+        _in::reachabilityFence(finalizable17);
+      }
+    }
+  }
+  i = 0;
+  for (ffi::Finalizable finalizable18 = new self::MyFinalizable::•(); [@vm.direct-call.metadata=dart.core::_IntegerImplementation.<] [@vm.inferred-type.metadata=dart.core::bool (skip check)] i.{core::num::<}(10){(core::num) → core::bool}; i = [@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(1){(core::num) → core::int}) {
+    _in::reachabilityFence(finalizable18);
+  }
+  _in::reachabilityFence(finalizable);
+}
+constants  {
+  #C1 = 1
+  #C2 = 2
+  #C3 = 3
+  #C4 = 4
+  #C5 = 5
+}
diff --git a/pkg/vm/testcases/transformations/ffi/finalizable_sync3.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/finalizable_sync3.dart.aot.expect
new file mode 100644
index 0000000..e4dd62a
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/finalizable_sync3.dart.aot.expect
@@ -0,0 +1,6 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+
+import "dart:ffi";
+
+static method main() → void {}
diff --git a/pkg/vm/testcases/transformations/ffi/finalizable_sync_star.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/finalizable_sync_star.dart.aot.expect
new file mode 100644
index 0000000..714dc60
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/finalizable_sync_star.dart.aot.expect
@@ -0,0 +1,50 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:_internal" as _in;
+
+import "dart:ffi";
+
+class MyFinalizable extends core::Object implements ffi::Finalizable {
+  synthetic constructor •() → self::MyFinalizable
+    : super core::Object::•()
+    ;
+}
+[@vm.unboxing-info.metadata=()->i]static method doSomething() → core::int
+  return 3;
+static method useFinalizableSyncStar([@vm.inferred-type.metadata=#lib::MyFinalizable] ffi::Finalizable finalizable) → core::Iterable<core::int> sync* {
+  yield block {
+    final core::int :expressionValueWrappedFinalizable = self::doSomething();
+    _in::reachabilityFence(finalizable);
+  } =>:expressionValueWrappedFinalizable;
+  final self::MyFinalizable finalizable2 = new self::MyFinalizable::•();
+  yield block {
+    final core::int :expressionValueWrappedFinalizable = 5;
+    _in::reachabilityFence(finalizable);
+    _in::reachabilityFence(finalizable2);
+  } =>:expressionValueWrappedFinalizable;
+  final self::MyFinalizable finalizable3 = new self::MyFinalizable::•();
+  yield block {
+    final core::int :expressionValueWrappedFinalizable = 10;
+    _in::reachabilityFence(finalizable);
+    _in::reachabilityFence(finalizable2);
+    _in::reachabilityFence(finalizable3);
+  } =>:expressionValueWrappedFinalizable;
+  _in::reachabilityFence(finalizable2);
+  _in::reachabilityFence(finalizable3);
+  _in::reachabilityFence(finalizable);
+}
+static method main() → void {
+  final self::MyFinalizable finalizable = new self::MyFinalizable::•();
+  {
+    core::Iterator<core::int> :sync-for-iterator = [@vm.direct-call.metadata=dart.async::_SyncStarIterable.iterator] [@vm.inferred-type.metadata=dart.async::_SyncStarIterator] [@vm.inferred-type.metadata=dart.async::_SyncStarIterable] self::useFinalizableSyncStar(finalizable).{core::Iterable::iterator}{core::Iterator<core::int>};
+    for (; [@vm.direct-call.metadata=dart.async::_SyncStarIterator.moveNext] [@vm.inferred-type.metadata=dart.core::bool? (skip check)] :sync-for-iterator.{core::Iterator::moveNext}(){() → core::bool}; ) {
+      final core::int element = [@vm.direct-call.metadata=dart.async::_SyncStarIterator.current] [@vm.inferred-type.metadata=int?] :sync-for-iterator.{core::Iterator::current}{core::int};
+      {
+        core::print(element);
+      }
+    }
+  }
+  _in::reachabilityFence(finalizable);
+}
diff --git a/pkg/vm/testcases/transformations/ffi/regress_49075.dart.aot.expect b/pkg/vm/testcases/transformations/ffi/regress_49075.dart.aot.expect
new file mode 100644
index 0000000..fb88d53
--- /dev/null
+++ b/pkg/vm/testcases/transformations/ffi/regress_49075.dart.aot.expect
@@ -0,0 +1,20 @@
+library #lib /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "dart:ffi" as ffi;
+import "dart:_internal" as _in;
+import "dart:async" as asy;
+
+import "dart:ffi";
+
+class MyFinalizable extends core::Object implements ffi::Finalizable {
+  constructor •() → self::MyFinalizable
+    : super core::Object::•() {
+    ;
+    _in::reachabilityFence(this);
+  }
+}
+static method main(core::List<core::String> arguments) → asy::Future<void> async /* futureValueType= void */ {
+  final self::MyFinalizable myFinalizable = await new self::MyFinalizable::•();
+  _in::reachabilityFence(myFinalizable);
+}