[ffigen] Use stret and fpret variants of objc_msgSend where appropriate (#613)

* Use stret and fpret variants of objc_msgSend where appropriate

* Changelog

* Daco's comment
diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md
index 7036a8c..b90e619 100644
--- a/pkgs/ffigen/CHANGELOG.md
+++ b/pkgs/ffigen/CHANGELOG.md
@@ -2,6 +2,9 @@
 
 - Add support for ObjC Blocks that can be invoked from any thread, using
   NativeCallable.listener.
+- Fix invalid exceptional return value ObjCBlocks that return floats.
+- Fix return_of_invalid_type analysis error for ObjCBlocks.
+- Fix crash in ObjC methods and blocks that return structs by value.
 - Bump min SDK version to 3.2.0-114.0.dev.
 
 # 9.0.1
diff --git a/pkgs/ffigen/example/swift/swift_api.h b/pkgs/ffigen/example/swift/swift_api.h
index 0928c49..14ebedf 100644
--- a/pkgs/ffigen/example/swift/swift_api.h
+++ b/pkgs/ffigen/example/swift/swift_api.h
@@ -1,4 +1,4 @@
-// Generated by Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51)
+// Generated by Apple Swift version 5.8.1 (swiftlang-5.8.0.124.5 clang-1403.0.22.11.100)
 #ifndef SWIFT_MODULE_SWIFT_H
 #define SWIFT_MODULE_SWIFT_H
 #pragma clang diagnostic push
@@ -21,7 +21,6 @@
 # include <swift/objc-prologue.h>
 #endif
 
-#pragma clang diagnostic ignored "-Wduplicate-method-match"
 #pragma clang diagnostic ignored "-Wauto-import"
 #if defined(__OBJC__)
 #include <Foundation/Foundation.h>
@@ -30,10 +29,24 @@
 #include <cstdint>
 #include <cstddef>
 #include <cstdbool>
+#include <cstring>
+#include <stdlib.h>
+#include <new>
+#include <type_traits>
 #else
 #include <stdint.h>
 #include <stddef.h>
 #include <stdbool.h>
+#include <string.h>
+#endif
+#if defined(__cplusplus)
+#if __has_include(<ptrauth.h>)
+# include <ptrauth.h>
+#else
+# ifndef __ptrauth_swift_value_witness_function_pointer
+#  define __ptrauth_swift_value_witness_function_pointer(x)
+# endif
+#endif
 #endif
 
 #if !defined(SWIFT_TYPEDEFS)
@@ -69,53 +82,66 @@
 # if __has_feature(objc_class_property)
 #  define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__
 # else
-#  define SWIFT_CLASS_PROPERTY(...)
+#  define SWIFT_CLASS_PROPERTY(...) 
 # endif
 #endif
-
-#if __has_attribute(objc_runtime_name)
-# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X)))
-#else
-# define SWIFT_RUNTIME_NAME(X)
+#if !defined(SWIFT_RUNTIME_NAME)
+# if __has_attribute(objc_runtime_name)
+#  define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X)))
+# else
+#  define SWIFT_RUNTIME_NAME(X) 
+# endif
 #endif
-#if __has_attribute(swift_name)
-# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X)))
-#else
-# define SWIFT_COMPILE_NAME(X)
+#if !defined(SWIFT_COMPILE_NAME)
+# if __has_attribute(swift_name)
+#  define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X)))
+# else
+#  define SWIFT_COMPILE_NAME(X) 
+# endif
 #endif
-#if __has_attribute(objc_method_family)
-# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X)))
-#else
-# define SWIFT_METHOD_FAMILY(X)
+#if !defined(SWIFT_METHOD_FAMILY)
+# if __has_attribute(objc_method_family)
+#  define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X)))
+# else
+#  define SWIFT_METHOD_FAMILY(X) 
+# endif
 #endif
-#if __has_attribute(noescape)
-# define SWIFT_NOESCAPE __attribute__((noescape))
-#else
-# define SWIFT_NOESCAPE
+#if !defined(SWIFT_NOESCAPE)
+# if __has_attribute(noescape)
+#  define SWIFT_NOESCAPE __attribute__((noescape))
+# else
+#  define SWIFT_NOESCAPE 
+# endif
 #endif
-#if __has_attribute(ns_consumed)
-# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed))
-#else
-# define SWIFT_RELEASES_ARGUMENT
+#if !defined(SWIFT_RELEASES_ARGUMENT)
+# if __has_attribute(ns_consumed)
+#  define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed))
+# else
+#  define SWIFT_RELEASES_ARGUMENT 
+# endif
 #endif
-#if __has_attribute(warn_unused_result)
-# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
-#else
-# define SWIFT_WARN_UNUSED_RESULT
+#if !defined(SWIFT_WARN_UNUSED_RESULT)
+# if __has_attribute(warn_unused_result)
+#  define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+# else
+#  define SWIFT_WARN_UNUSED_RESULT 
+# endif
 #endif
-#if __has_attribute(noreturn)
-# define SWIFT_NORETURN __attribute__((noreturn))
-#else
-# define SWIFT_NORETURN
+#if !defined(SWIFT_NORETURN)
+# if __has_attribute(noreturn)
+#  define SWIFT_NORETURN __attribute__((noreturn))
+# else
+#  define SWIFT_NORETURN 
+# endif
 #endif
 #if !defined(SWIFT_CLASS_EXTRA)
-# define SWIFT_CLASS_EXTRA
+# define SWIFT_CLASS_EXTRA 
 #endif
 #if !defined(SWIFT_PROTOCOL_EXTRA)
-# define SWIFT_PROTOCOL_EXTRA
+# define SWIFT_PROTOCOL_EXTRA 
 #endif
 #if !defined(SWIFT_ENUM_EXTRA)
-# define SWIFT_ENUM_EXTRA
+# define SWIFT_ENUM_EXTRA 
 #endif
 #if !defined(SWIFT_CLASS)
 # if __has_attribute(objc_subclassing_restricted)
@@ -135,28 +161,25 @@
 #  define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME)
 # endif
 #endif
-
 #if !defined(SWIFT_PROTOCOL)
 # define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
 # define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
 #endif
-
 #if !defined(SWIFT_EXTENSION)
 # define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__)
 #endif
-
 #if !defined(OBJC_DESIGNATED_INITIALIZER)
 # if __has_attribute(objc_designated_initializer)
 #  define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
 # else
-#  define OBJC_DESIGNATED_INITIALIZER
+#  define OBJC_DESIGNATED_INITIALIZER 
 # endif
 #endif
 #if !defined(SWIFT_ENUM_ATTR)
-# if defined(__has_attribute) && __has_attribute(enum_extensibility)
+# if __has_attribute(enum_extensibility)
 #  define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility)))
 # else
-#  define SWIFT_ENUM_ATTR(_extensibility)
+#  define SWIFT_ENUM_ATTR(_extensibility) 
 # endif
 #endif
 #if !defined(SWIFT_ENUM)
@@ -185,14 +208,16 @@
 #if !defined(SWIFT_DEPRECATED_MSG)
 # define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__)))
 #endif
-#if __has_feature(attribute_diagnose_if_objc)
-# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning")))
-#else
-# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg)
+#if !defined(SWIFT_DEPRECATED_OBJC)
+# if __has_feature(attribute_diagnose_if_objc)
+#  define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning")))
+# else
+#  define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg)
+# endif
 #endif
 #if defined(__OBJC__)
 #if !defined(IBSegueAction)
-# define IBSegueAction
+# define IBSegueAction 
 #endif
 #endif
 #if !defined(SWIFT_EXTERN)
@@ -205,26 +230,31 @@
 #if !defined(SWIFT_CALL)
 # define SWIFT_CALL __attribute__((swiftcall))
 #endif
-#if defined(__cplusplus)
-#if !defined(SWIFT_NOEXCEPT)
-# define SWIFT_NOEXCEPT noexcept
+#if !defined(SWIFT_INDIRECT_RESULT)
+# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result))
 #endif
+#if !defined(SWIFT_CONTEXT)
+# define SWIFT_CONTEXT __attribute__((swift_context))
+#endif
+#if !defined(SWIFT_ERROR_RESULT)
+# define SWIFT_ERROR_RESULT __attribute__((swift_error_result))
+#endif
+#if defined(__cplusplus)
+# define SWIFT_NOEXCEPT noexcept
 #else
-#if !defined(SWIFT_NOEXCEPT)
 # define SWIFT_NOEXCEPT 
 #endif
+#if defined(_WIN32)
+#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL)
+# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport)
 #endif
-#if defined(__cplusplus)
-#if !defined(SWIFT_CXX_INT_DEFINED)
-#define SWIFT_CXX_INT_DEFINED
-namespace swift {
-using Int = ptrdiff_t;
-using UInt = size_t;
-}
+#else
+#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL)
+# define SWIFT_IMPORT_STDLIB_SYMBOL 
 #endif
 #endif
 #if defined(__OBJC__)
-#if __has_feature(modules)
+#if __has_feature(objc_modules)
 #if __has_warning("-Watimport-in-framework-header")
 #pragma clang diagnostic ignored "-Watimport-in-framework-header"
 #endif
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
index 05cd130..b092e73 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart
@@ -94,24 +94,18 @@
     _blockReleaseFunc,
   );
 
-  // We need to load a separate instance of objc_msgSend for each signature.
-  final _msgSendFuncs = <String, Func>{};
-  Func getMsgSendFunc(Type returnType, List<ObjCMethodParam> params) {
+  // We need to load a separate instance of objc_msgSend for each signature. If
+  // the return type is a struct, we need to use objc_msgSend_stret instead, and
+  // for float return types we need objc_msgSend_fpret.
+  final _msgSendFuncs = <String, ObjCMsgSendFunc>{};
+  ObjCMsgSendFunc getMsgSendFunc(
+      Type returnType, List<ObjCMethodParam> params) {
     var key = returnType.cacheKey();
     for (final p in params) {
       key += ' ${p.type.cacheKey()}';
     }
-    return _msgSendFuncs[key] ??= Func(
-      name: '_objc_msgSend_${_msgSendFuncs.length}',
-      originalName: 'objc_msgSend',
-      returnType: returnType,
-      parameters: [
-        Parameter(name: 'obj', type: PointerType(objCObjectType)),
-        Parameter(name: 'sel', type: PointerType(objCSelType)),
-        for (final p in params) Parameter(name: p.name, type: p.type),
-      ],
-      isInternal: true,
-    );
+    return _msgSendFuncs[key] ??= ObjCMsgSendFunc(
+        '_objc_msgSend_${_msgSendFuncs.length}', returnType, params);
   }
 
   final _selObjects = <String, ObjCInternalGlobal>{};
@@ -285,8 +279,8 @@
     _retainFunc.addDependencies(dependencies);
     _releaseFunc.addDependencies(dependencies);
     _releaseFinalizer.addDependencies(dependencies);
-    for (final func in _msgSendFuncs.values) {
-      func.addDependencies(dependencies);
+    for (final msgSendFunc in _msgSendFuncs.values) {
+      msgSendFunc.func.addDependencies(dependencies);
     }
     for (final sel in _selObjects.values) {
       sel.addDependencies(dependencies);
@@ -396,3 +390,50 @@
     binding?.addDependencies(dependencies);
   }
 }
+
+enum ObjCMsgSendVariant {
+  normal('objc_msgSend'),
+  stret('objc_msgSend_stret'),
+  fpret('objc_msgSend_fpret');
+
+  final String name;
+  const ObjCMsgSendVariant(this.name);
+
+  static ObjCMsgSendVariant fromReturnType(Type returnType) {
+    if (returnType is Compound && returnType.isStruct) {
+      return ObjCMsgSendVariant.stret;
+    } else if (returnType == floatType || returnType == doubleType) {
+      return ObjCMsgSendVariant.fpret;
+    }
+    return ObjCMsgSendVariant.normal;
+  }
+}
+
+/// A wrapper around the objc_msgSend function, or the stret or fpret variants.
+class ObjCMsgSendFunc {
+  final ObjCMsgSendVariant variant;
+  late final Func func;
+
+  ObjCMsgSendFunc(String name, Type returnType, List<ObjCMethodParam> params)
+      : variant = ObjCMsgSendVariant.fromReturnType(returnType) {
+    func = Func(
+      name: name,
+      originalName: variant.name,
+      returnType: isStret ? voidType : returnType,
+      parameters: [
+        if (isStret) Parameter(name: 'stret', type: PointerType(returnType)),
+        Parameter(name: 'obj', type: PointerType(objCObjectType)),
+        Parameter(name: 'sel', type: PointerType(objCSelType)),
+        for (final p in params) Parameter(name: p.name, type: p.type),
+      ],
+      isInternal: true,
+    );
+  }
+
+  String get name => func.name;
+  bool get isStret => variant == ObjCMsgSendVariant.stret;
+
+  void addDependencies(Set<Binding> dependencies) {
+    func.addDependencies(dependencies);
+  }
+}
diff --git a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
index 2cf44af..bf4d887 100644
--- a/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
+++ b/pkgs/ffigen/lib/src/code_generator/objc_interface.dart
@@ -46,7 +46,7 @@
   final ObjCBuiltInFunctions builtInFunctions;
   late final ObjCInternalGlobal _classObject;
   late final ObjCInternalGlobal _isKindOfClass;
-  late final Func _isKindOfClassMsgSend;
+  late final ObjCMsgSendFunc _isKindOfClassMsgSend;
 
   ObjCInterface({
     String? usr,
@@ -130,7 +130,14 @@
     for (final m in methods.values) {
       final methodName = m._getDartMethodName(uniqueNamer);
       final isStatic = m.isClass;
-      final returnType = m.returnType;
+      final isStret = m.msgSend!.isStret;
+
+      var returnType = m.returnType;
+      var params = m.params;
+      if (isStret) {
+        params = [ObjCMethodParam(PointerType(returnType), 'stret'), ...params];
+        returnType = voidType;
+      }
 
       // The method declaration.
       if (m.dartDoc != null) {
@@ -159,7 +166,7 @@
             s.write(methodName[0].toUpperCase() + methodName.substring(1));
             break;
         }
-        s.write(paramsToString(m.params, isStatic: true));
+        s.write(paramsToString(params, isStatic: true));
       } else {
         if (superType?.methods[m.originalName]?.sameAs(m) ?? false) {
           s.write('@override\n  ');
@@ -170,18 +177,25 @@
             s.write(_getConvertedReturnType(
                 returnType, w, name, m.isNullableReturn));
             s.write(' $methodName');
-            s.write(paramsToString(m.params, isStatic: false));
+            s.write(paramsToString(params, isStatic: false));
             break;
           case ObjCMethodKind.propertyGetter:
-            // returnType get methodName
             s.write(_getConvertedReturnType(
                 returnType, w, name, m.isNullableReturn));
-            s.write(' get $methodName');
+            if (isStret) {
+              // void getMethodName(Pointer<returnType> stret, NativeLibrary _lib)
+              s.write(' get');
+              s.write(methodName[0].toUpperCase() + methodName.substring(1));
+              s.write(paramsToString(params, isStatic: false));
+            } else {
+              // returnType get methodName
+              s.write(' get $methodName');
+            }
             break;
           case ObjCMethodKind.propertySetter:
             // set methodName(...)
             s.write(' set $methodName');
-            s.write(paramsToString(m.params, isStatic: false));
+            s.write(paramsToString(params, isStatic: false));
             break;
         }
       }
@@ -192,10 +206,13 @@
       final convertReturn = m.kind != ObjCMethodKind.propertySetter &&
           _needsConverting(returnType);
 
-      if (returnType != NativeType(SupportedNativeType.Void)) {
+      if (returnType != voidType) {
         s.write('    ${convertReturn ? 'final _ret = ' : 'return '}');
       }
       s.write('_lib.${m.msgSend!.name}(');
+      if (isStret) {
+        s.write('stret, ');
+      }
       s.write(isStatic ? '_lib.${_classObject.name}' : '_id');
       s.write(', _lib.${m.selObject!.name}');
       for (final p in m.params) {
@@ -427,7 +444,7 @@
   final bool isClass;
   bool returnsRetained = false;
   ObjCInternalGlobal? selObject;
-  Func? msgSend;
+  ObjCMsgSendFunc? msgSend;
 
   ObjCMethod({
     required this.originalName,
diff --git a/pkgs/ffigen/test/native_objc_test/block_test.dart b/pkgs/ffigen/test/native_objc_test/block_test.dart
index 87a735a..908e8fc 100644
--- a/pkgs/ffigen/test/native_objc_test/block_test.dart
+++ b/pkgs/ffigen/test/native_objc_test/block_test.dart
@@ -109,6 +109,52 @@
       expect(BlockTester.callFloatBlock_(lib, block), closeTo(5.79, 1e-6));
     });
 
+    test('Double block', () {
+      final block = ObjCBlock3.fromFunction(lib, (double x) {
+        return x + 4.56;
+      });
+      expect(block(1.23), closeTo(5.79, 1e-6));
+      expect(BlockTester.callDoubleBlock_(lib, block), closeTo(5.79, 1e-6));
+    });
+
+    test('Struct block', () {
+      final inputPtr = calloc<Vec4>();
+      final input = inputPtr.ref;
+      input.x = 1.2;
+      input.y = 3.4;
+      input.z = 5.6;
+      input.w = 7.8;
+
+      final tempPtr = calloc<Vec4>();
+      final temp = tempPtr.ref;
+      final block = ObjCBlock4.fromFunction(lib, (Vec4 v) {
+        // Twiddle the Vec4 components.
+        temp.x = v.y;
+        temp.y = v.z;
+        temp.z = v.w;
+        temp.w = v.x;
+        return temp;
+      });
+
+      final result1 = block(input);
+      expect(result1.x, 3.4);
+      expect(result1.y, 5.6);
+      expect(result1.z, 7.8);
+      expect(result1.w, 1.2);
+
+      final result2Ptr = calloc<Vec4>();
+      final result2 = result2Ptr.ref;
+      BlockTester.callVec4Block_(lib, result2Ptr, block);
+      expect(result2.x, 3.4);
+      expect(result2.y, 5.6);
+      expect(result2.z, 7.8);
+      expect(result2.w, 1.2);
+
+      calloc.free(inputPtr);
+      calloc.free(tempPtr);
+      calloc.free(result2Ptr);
+    });
+
     Pointer<Void> funcPointerBlockRefCountTest() {
       final block = ObjCBlock1.fromFunctionPointer(
           lib, Pointer.fromFunction(_add100, 999));
diff --git a/pkgs/ffigen/test/native_objc_test/block_test.m b/pkgs/ffigen/test/native_objc_test/block_test.m
index 9e619b6..a864f2e 100644
--- a/pkgs/ffigen/test/native_objc_test/block_test.m
+++ b/pkgs/ffigen/test/native_objc_test/block_test.m
@@ -5,8 +5,17 @@
 #import <Foundation/NSObject.h>
 #import <Foundation/NSThread.h>
 
+typedef struct {
+  double x;
+  double y;
+  double z;
+  double w;
+} Vec4;
+
 typedef int32_t (^IntBlock)(int32_t);
 typedef float (^FloatBlock)(float);
+typedef double (^DoubleBlock)(double);
+typedef Vec4 (^Vec4Block)(Vec4);
 typedef void (^VoidBlock)();
 
 // Wrapper around a block, so that our Dart code can test creating and invoking
@@ -23,6 +32,8 @@
 + (void)callOnSameThread:(VoidBlock)block;
 + (NSThread*)callOnNewThread:(VoidBlock)block;
 + (float)callFloatBlock:(FloatBlock)block;
++ (double)callDoubleBlock:(DoubleBlock)block;
++ (Vec4)callVec4Block:(Vec4Block)block;
 @end
 
 @implementation BlockTester
@@ -92,4 +103,17 @@
   return block(1.23);
 }
 
++ (double)callDoubleBlock:(DoubleBlock)block {
+  return block(1.23);
+}
+
++ (Vec4)callVec4Block:(Vec4Block)block {
+  Vec4 vec4;
+  vec4.x = 1.2;
+  vec4.y = 3.4;
+  vec4.z = 5.6;
+  vec4.w = 7.8;
+  return block(vec4);
+}
+
 @end
diff --git a/pkgs/ffigen/test/native_objc_test/method_test.dart b/pkgs/ffigen/test/native_objc_test/method_test.dart
index ebb1fba..21ba7e0 100644
--- a/pkgs/ffigen/test/native_objc_test/method_test.dart
+++ b/pkgs/ffigen/test/native_objc_test/method_test.dart
@@ -8,13 +8,14 @@
 import 'dart:ffi';
 import 'dart:io';
 
+import 'package:ffi/ffi.dart';
 import 'package:test/test.dart';
 import '../test_utils.dart';
 import 'method_bindings.dart';
 import 'util.dart';
 
 void main() {
-  MethodInterface? testInstance;
+  late MethodInterface testInstance;
   late MethodTestObjCLibrary lib;
 
   group('method calls', () {
@@ -29,19 +30,19 @@
 
     group('Instance methods', () {
       test('No arguments', () {
-        expect(testInstance!.add(), 5);
+        expect(testInstance.add(), 5);
       });
 
       test('One argument', () {
-        expect(testInstance!.add_(23), 23);
+        expect(testInstance.add_(23), 23);
       });
 
       test('Two arguments', () {
-        expect(testInstance!.add_Y_(23, 17), 40);
+        expect(testInstance.add_Y_(23, 17), 40);
       });
 
       test('Three arguments', () {
-        expect(testInstance!.add_Y_Z_(23, 17, 60), 100);
+        expect(testInstance.add_Y_Z_(23, 17, 60), 100);
       });
     });
 
@@ -62,5 +63,35 @@
         expect(MethodInterface.sub_Y_Z_(lib, 10, 7, 3), -20);
       });
     });
+
+    group('Regress #608', () {
+      test('Structs', () {
+        final inputPtr = calloc<Vec4>();
+        final input = inputPtr.ref;
+        input.x = 1.2;
+        input.y = 3.4;
+        input.z = 5.6;
+        input.w = 7.8;
+
+        final resultPtr = calloc<Vec4>();
+        final result = resultPtr.ref;
+        testInstance.twiddleVec4Components_(resultPtr, input);
+        expect(result.x, 3.4);
+        expect(result.y, 5.6);
+        expect(result.z, 7.8);
+        expect(result.w, 1.2);
+
+        calloc.free(inputPtr);
+        calloc.free(resultPtr);
+      });
+
+      test('Floats', () {
+        expect(testInstance.addFloats_Y_(1.23, 4.56), closeTo(5.79, 1e-6));
+      });
+
+      test('Doubles', () {
+        expect(testInstance.addDoubles_Y_(1.23, 4.56), closeTo(5.79, 1e-6));
+      });
+    });
   });
 }
diff --git a/pkgs/ffigen/test/native_objc_test/method_test.m b/pkgs/ffigen/test/native_objc_test/method_test.m
index 86ea090..179f4ef 100644
--- a/pkgs/ffigen/test/native_objc_test/method_test.m
+++ b/pkgs/ffigen/test/native_objc_test/method_test.m
@@ -1,5 +1,12 @@
 #import <Foundation/NSObject.h>
 
+typedef struct {
+  double x;
+  double y;
+  double z;
+  double w;
+} Vec4;
+
 @interface MethodInterface : NSObject {
 }
 
@@ -14,6 +21,10 @@
 +(int32_t)sub:(int32_t)x Y:(int32_t) y;
 +(int32_t)sub:(int32_t)x Y:(int32_t) y Z:(int32_t) z;
 
+-(Vec4)twiddleVec4Components:(Vec4)v;
+-(float)addFloats:(float)x Y:(float) y;
+-(double)addDoubles:(double)x Y:(double) y;
+
 @end
 
 @implementation MethodInterface
@@ -50,4 +61,21 @@
     return - x - y - z;
 }
 
+-(Vec4)twiddleVec4Components:(Vec4)v {
+    Vec4 u;
+    u.x = v.y;
+    u.y = v.z;
+    u.z = v.w;
+    u.w = v.x;
+    return u;
+}
+
+-(float)addFloats:(float)x Y:(float) y {
+    return x + y;
+}
+
+-(double)addDoubles:(double)x Y:(double) y {
+    return x + y;
+}
+
 @end
diff --git a/pkgs/ffigen/test/native_objc_test/property_test.dart b/pkgs/ffigen/test/native_objc_test/property_test.dart
index 72c11e0..9bd4f3c 100644
--- a/pkgs/ffigen/test/native_objc_test/property_test.dart
+++ b/pkgs/ffigen/test/native_objc_test/property_test.dart
@@ -8,13 +8,14 @@
 import 'dart:ffi';
 import 'dart:io';
 
+import 'package:ffi/ffi.dart';
 import 'package:test/test.dart';
 import '../test_utils.dart';
 import 'property_bindings.dart';
 import 'util.dart';
 
 void main() {
-  PropertyInterface? testInstance;
+  late PropertyInterface testInstance;
   late PropertyTestObjCLibrary lib;
 
   group('properties', () {
@@ -29,12 +30,12 @@
 
     group('instance properties', () {
       test('read-only property', () {
-        expect(testInstance!.readOnlyProperty, 7);
+        expect(testInstance.readOnlyProperty, 7);
       });
 
       test('read-write property', () {
-        testInstance!.readWriteProperty = 23;
-        expect(testInstance!.readWriteProperty, 23);
+        testInstance.readWriteProperty = 23;
+        expect(testInstance.readWriteProperty, 23);
       });
     });
 
@@ -48,5 +49,38 @@
         expect(PropertyInterface.getClassReadWriteProperty(lib), 101);
       });
     });
+
+    group('Regress #608', () {
+      test('Structs', () {
+        final inputPtr = calloc<Vec4>();
+        final input = inputPtr.ref;
+        input.x = 1.2;
+        input.y = 3.4;
+        input.z = 5.6;
+        input.w = 7.8;
+
+        final resultPtr = calloc<Vec4>();
+        final result = resultPtr.ref;
+        testInstance.structProperty = input;
+        testInstance.getStructProperty(resultPtr);
+        expect(result.x, 1.2);
+        expect(result.y, 3.4);
+        expect(result.z, 5.6);
+        expect(result.w, 7.8);
+
+        calloc.free(inputPtr);
+        calloc.free(resultPtr);
+      });
+
+      test('Floats', () {
+        testInstance.floatProperty = 1.23;
+        expect(testInstance.floatProperty, closeTo(1.23, 1e-6));
+      });
+
+      test('Doubles', () {
+        testInstance.doubleProperty = 1.23;
+        expect(testInstance.doubleProperty, 1.23);
+      });
+    });
   });
 }
diff --git a/pkgs/ffigen/test/native_objc_test/property_test.m b/pkgs/ffigen/test/native_objc_test/property_test.m
index 117c6d5..0085034 100644
--- a/pkgs/ffigen/test/native_objc_test/property_test.m
+++ b/pkgs/ffigen/test/native_objc_test/property_test.m
@@ -2,6 +2,13 @@
 
 @class UndefinedTemplate<ObjectType>;
 
+typedef struct {
+  double x;
+  double y;
+  double z;
+  double w;
+} Vec4;
+
 @interface PropertyInterface : NSObject {
 }
 
@@ -10,6 +17,9 @@
 @property (class, readonly, copy) UndefinedTemplate<NSString *> *regressGH436;
 @property (class, readonly) int32_t classReadOnlyProperty;
 @property (class) int32_t classReadWriteProperty;
+@property float floatProperty;
+@property double doubleProperty;
+@property Vec4 structProperty;
 
 @end
 
@@ -33,4 +43,4 @@
   _classReadWriteProperty = x;
 }
 
-@end
\ No newline at end of file
+@end