From 8b6452b4fc051c23df85fe802d0589213bb313c6 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 6 May 2026 09:46:07 +0200 Subject: [PATCH 1/4] [src] Add [Un]SupportedSimulator attributes. Add two attributes to specify whether an API is available in the simulator: * [UnsupportedSimulator ("")]: API is not available. * [SupportedSimulator ("[osversion]")]: API is available, optionally only in the specified OS version or not. If no attribute is found, then the API is available in the simulator. Unlike the normal availability attributes, specifying simulator availability for one platform does not mean anything for any other platforms. --- src/Metal/MTLEnums.cs | 6 + .../SupportedSimulatorAttribute.cs | 44 ++++++ src/TrimAttributes.LinkDescription.xml | 12 ++ src/bgen/AttributeManager.cs | 4 + src/bgen/Enums.cs | 1 + src/bgen/Generator.cs | 75 +++++++++- src/bgen/bgen.csproj | 1 + src/frameworks.sources | 1 + src/metal.cs | 129 ++++++++++++++++++ src/metalfx.cs | 4 + src/videotoolbox.cs | 4 + tests/bgen/BGenTests.cs | 67 +++++++++ .../simulator-availability-attributes.cs | 30 ++++ tests/common/PlatformInfo.cs | 46 +++++++ tests/introspection/ApiBaseTest.cs | 1 + tests/introspection/ApiCtorInitTest.cs | 45 ------ 16 files changed, 424 insertions(+), 46 deletions(-) create mode 100644 src/ObjCRuntime/SupportedSimulatorAttribute.cs create mode 100644 tests/bgen/tests/simulator-availability-attributes.cs diff --git a/src/Metal/MTLEnums.cs b/src/Metal/MTLEnums.cs index b6f15a43630f..19ea79b3fc6f 100644 --- a/src/Metal/MTLEnums.cs +++ b/src/Metal/MTLEnums.cs @@ -2358,6 +2358,8 @@ public enum MTLIOCompressionStatus : long { [Mac (13, 0), iOS (16, 0), MacCatalyst (16, 0), TV (16, 0)] [Native] [ErrorDomain ("MTLIOErrorDomain")] + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] public enum MTLIOError : long { UrlInvalid = 1, Internal = 2, @@ -2466,6 +2468,8 @@ public enum MTL4BlendState : long { [Mac (26, 0), iOS (26, 0), MacCatalyst (26, 0), TV (26, 0)] [Native] [ErrorDomain ("MTL4CommandQueueErrorDomain")] + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] public enum MTL4CommandQueueError : long { None = 0, Timeout = 1, @@ -2599,6 +2603,8 @@ public enum MTLDeviceError : long { [Mac (26, 0), iOS (26, 0), MacCatalyst (26, 0), TV (26, 0)] [Native] [ErrorDomain ("MTLTensorDomain")] + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] public enum MTLTensorError : long { None = 0, InternalError = 1, diff --git a/src/ObjCRuntime/SupportedSimulatorAttribute.cs b/src/ObjCRuntime/SupportedSimulatorAttribute.cs new file mode 100644 index 000000000000..25e844c6e341 --- /dev/null +++ b/src/ObjCRuntime/SupportedSimulatorAttribute.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +namespace ObjCRuntime { + /// Indicates that an API is supported for a specified platform. If a version is specified, the API is available starting in the specified OS version. Multiple attributes can be applied to indicate support on multiple operating systems. + /// + /// Contrary to standard availability attributes (such as attributes), the presence of this attribute for some platforms does not imply any meaning for other platforms. + /// If there are no or attributes on an API, the API is assumed to be available in the simulator. + /// This attribute will be trimmed away if the app is trimmed. + /// + [AttributeUsage (AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor | AttributeTargets.Field, AllowMultiple = true)] + public sealed class SupportedSimulatorAttribute : Attribute { + /// Initializes a new attribute. + /// The platform where this API is supported in the simulator. Optionally specifies a platform version. + public SupportedSimulatorAttribute (string platformName) + { + PlatformName = platformName; + } + + /// The name of the platform. + public string PlatformName { get; set; } + } + + /// Indicates that an API is not supported in the simulator for the specified platform. Multiple attributes can be applied to indicate lack of support on multiple platforms. + /// + /// Contrary to standard availability attributes (such as attributes), the presence of this attribute for some platforms does not imply any meaning for other platforms. + /// If there are no or attributes on an API, the API is assumed to be available in the simulator. + /// This attribute will be trimmed away if the app is trimmed. + /// + [AttributeUsage (AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Constructor | AttributeTargets.Field, AllowMultiple = true)] + public sealed class UnsupportedSimulatorAttribute : Attribute { + /// Initializes a new attribute. + /// The platform where this API is not supported in the simulator. + public UnsupportedSimulatorAttribute (string platformName) + { + PlatformName = platformName; + } + + /// The name of the platform. + public string PlatformName { get; set; } + } +} diff --git a/src/TrimAttributes.LinkDescription.xml b/src/TrimAttributes.LinkDescription.xml index bfe5a67b8964..b38fba735286 100644 --- a/src/TrimAttributes.LinkDescription.xml +++ b/src/TrimAttributes.LinkDescription.xml @@ -290,6 +290,18 @@ + + + + + + + + + + + + diff --git a/src/bgen/AttributeManager.cs b/src/bgen/AttributeManager.cs index d7aadca355f7..3ceccdd0c76e 100644 --- a/src/bgen/AttributeManager.cs +++ b/src/bgen/AttributeManager.cs @@ -142,6 +142,10 @@ public AttributeManager (TypeCache typeCache) return typeof (ObjCRuntime.RequiresSuperAttribute); case "ObjCRuntime.ObjectiveCFrameworkAttribute": return typeof (ObjCRuntime.ObjectiveCFrameworkAttribute); + case "ObjCRuntime.SupportedSimulatorAttribute": + return typeof (ObjCRuntime.SupportedSimulatorAttribute); + case "ObjCRuntime.UnsupportedSimulatorAttribute": + return typeof (ObjCRuntime.UnsupportedSimulatorAttribute); case "UnavailableAttribute": return typeof (UnavailableAttribute); case "OptionalImplementationAttribute": diff --git a/src/bgen/Enums.cs b/src/bgen/Enums.cs index e2aa70af6080..b6c452eb8052 100644 --- a/src/bgen/Enums.cs +++ b/src/bgen/Enums.cs @@ -200,6 +200,7 @@ void GenerateEnum (Type type) } } // the *Extensions has the same version requirement as the enum itself + PrintSimulatorAvailabilityAttributes (type); PrintPlatformAttributes (type); PrintExperimentalAttribute (type); print_generated_code (); diff --git a/src/bgen/Generator.cs b/src/bgen/Generator.cs index 9a2099414299..60d050caf744 100644 --- a/src/bgen/Generator.cs +++ b/src/bgen/Generator.cs @@ -5547,7 +5547,7 @@ public void PrintBindAsAttribute (ICustomAttributeProvider? mi, StringBuilder? s // Not adding the experimental attribute is bad (it would mean that an API // we meant to be experimental ended up being released as stable), so it's // opt-out instead of opt-in. - public void PrintAttributes (ICustomAttributeProvider? mi, bool platform = false, bool preserve = false, bool advice = false, bool notImplemented = false, bool bindAs = false, bool requiresSuper = false, Type? inlinedType = null, bool experimental = true, bool obsolete = false, bool objectiveCFramework = false) + public void PrintAttributes (ICustomAttributeProvider? mi, bool platform = false, bool preserve = false, bool advice = false, bool notImplemented = false, bool bindAs = false, bool requiresSuper = false, Type? inlinedType = null, bool experimental = true, bool obsolete = false, bool objectiveCFramework = false, bool simulatorAvailability = true) { if (platform) PrintPlatformAttributes (mi as MemberInfo, inlinedType); @@ -5567,6 +5567,8 @@ public void PrintAttributes (ICustomAttributeProvider? mi, bool platform = false PrintObsoleteAttributes (mi); if (objectiveCFramework) PrintObjectiveCFrameworkAttribute (mi); + if (simulatorAvailability) + PrintSimulatorAvailabilityAttributes (mi); } public void PrintExperimentalAttribute (ICustomAttributeProvider? mi) @@ -5585,6 +5587,77 @@ public void PrintObjectiveCFrameworkAttribute (ICustomAttributeProvider? mi) print ($"[ObjectiveCFramework (\"{attrib.Framework}\")]"); } + public void PrintSimulatorAvailabilityAttributes (ICustomAttributeProvider? provider) + { + switch (CurrentPlatform) { + case PlatformName.MacCatalyst: + case PlatformName.MacOSX: + return; + case PlatformName.iOS: + case PlatformName.TvOS: + break; + default: + throw new BindingException (1047, CurrentPlatform); + } + + PrintSupportedSimulatorAttribute (provider); + PrintUnsupportedSimulatorAttribute (provider); + } + + void PrintSupportedSimulatorAttribute (ICustomAttributeProvider? provider) + { + var attribs = AttributeManager.GetCustomAttributes (provider); + if (attribs?.Any () != true) + return; + + // Only print the attribute for the current platform, we don't care about other platforms. + foreach (var attrib in attribs) { + switch (CurrentPlatform) { + case PlatformName.MacCatalyst: + case PlatformName.MacOSX: + break; + case PlatformName.iOS: + if (!attrib.PlatformName.StartsWith ("ios", StringComparison.OrdinalIgnoreCase)) + continue; + break; + case PlatformName.TvOS: + if (!attrib.PlatformName.StartsWith ("tvos", StringComparison.OrdinalIgnoreCase)) + continue; + break; + default: + throw new BindingException (1047, CurrentPlatform); + } + print ($"[SupportedSimulator (\"{attrib.PlatformName}\")]"); + } + } + + void PrintUnsupportedSimulatorAttribute (ICustomAttributeProvider? provider) + { + var attribs = AttributeManager.GetCustomAttributes (provider); + if (attribs?.Any () != true) + return; + + // Only print the attribute for the current platform, we don't care about other platforms. + foreach (var attrib in attribs) { + switch (CurrentPlatform) { + case PlatformName.MacCatalyst: + case PlatformName.MacOSX: + break; + case PlatformName.iOS: + if (!attrib.PlatformName.StartsWith ("ios", StringComparison.OrdinalIgnoreCase)) + continue; + break; + case PlatformName.TvOS: + if (!attrib.PlatformName.StartsWith ("tvos", StringComparison.OrdinalIgnoreCase)) + continue; + break; + default: + throw new BindingException (1047, CurrentPlatform); + } + print ($"[UnsupportedSimulator (\"{attrib.PlatformName}\")]"); + } + } + bool WriteDocumentation (MemberInfo info, Func? transformNode = null) { return DocumentationManager.WriteDocumentation (sw!, indent, info, transformNode); diff --git a/src/bgen/bgen.csproj b/src/bgen/bgen.csproj index e8b0fed1788c..10014d52ade5 100644 --- a/src/bgen/bgen.csproj +++ b/src/bgen/bgen.csproj @@ -59,6 +59,7 @@ + diff --git a/src/frameworks.sources b/src/frameworks.sources index 86320fd61d35..49e06425272d 100644 --- a/src/frameworks.sources +++ b/src/frameworks.sources @@ -1912,6 +1912,7 @@ SHARED_CORE_SOURCES = \ ObjCRuntime/Registrar.core.cs \ ObjCRuntime/RequiresSuperAttribute.cs \ ObjCRuntime/Selector.cs \ + ObjCRuntime/SupportedSimulatorAttribute.cs \ ObjCRuntime/SystemVersion.cs \ ObjCRuntime/ThrowHelper.cs \ Simd/MathHelper.cs \ diff --git a/src/metal.cs b/src/metal.cs index 4e67fa75aeab..b29676ad9f8b 100644 --- a/src/metal.cs +++ b/src/metal.cs @@ -5926,6 +5926,8 @@ interface MTLIndirectCommandBuffer : MTLResource { MTLResourceId GpuResourceID { get; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [iOS (13, 0), TV (13, 0)] [MacCatalyst (13, 1)] [BaseType (typeof (NSObject))] @@ -7804,6 +7806,8 @@ interface IMTLResidencySet { } interface IMTL4Archive { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4Archive { @@ -7843,6 +7847,9 @@ interface MTL4Archive { } interface IMTL4ArgumentTable { } + + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4ArgumentTable { @@ -7877,6 +7884,8 @@ interface MTL4ArgumentTable { interface IMTL4BinaryFunction { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4BinaryFunction { @@ -7891,6 +7900,8 @@ interface MTL4BinaryFunction { interface IMTL4CommandAllocator { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4CommandAllocator { @@ -7913,6 +7924,8 @@ interface MTL4CommandAllocator { interface IMTL4CommandBuffer { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4CommandBuffer { @@ -7983,6 +7996,8 @@ interface MTL4CommandBuffer { interface IMTL4CommandEncoder { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4CommandEncoder { @@ -8033,6 +8048,8 @@ interface MTL4CommandEncoder { interface IMTL4CommandQueue { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4CommandQueue { @@ -8103,6 +8120,8 @@ interface MTL4CommandQueue { interface IMTL4CommitFeedback { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4CommitFeedback { @@ -8122,6 +8141,8 @@ interface MTL4CommitFeedback { interface IMTL4CompilerTask { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4CompilerTask { @@ -8149,6 +8170,8 @@ interface MTL4CompilerTask { interface IMTL4Compiler { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4Compiler { @@ -8277,6 +8300,8 @@ interface MTL4Compiler { interface IMTL4ComputeCommandEncoder { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4ComputeCommandEncoder : MTL4CommandEncoder { @@ -8427,6 +8452,8 @@ interface MTL4ComputeCommandEncoder : MTL4CommandEncoder { interface IMTL4CounterHeap { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4CounterHeap { @@ -8454,6 +8481,8 @@ interface MTL4CounterHeap { interface IMTL4MachineLearningCommandEncoder { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4MachineLearningCommandEncoder : MTL4CommandEncoder { @@ -8472,6 +8501,8 @@ interface MTL4MachineLearningCommandEncoder : MTL4CommandEncoder { interface IMTL4MachineLearningPipelineState { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4MachineLearningPipelineState : MTLAllocation { @@ -8494,6 +8525,8 @@ interface MTL4MachineLearningPipelineState : MTLAllocation { interface IMTL4PipelineDataSetSerializer { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4PipelineDataSetSerializer { @@ -8509,6 +8542,8 @@ interface MTL4PipelineDataSetSerializer { interface IMTL4RenderCommandEncoder { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [Protocol (BackwardsCompatibleCodeGeneration = false)] interface MTL4RenderCommandEncoder : MTL4CommandEncoder { @@ -8782,6 +8817,8 @@ interface MTLTextureViewPool : MTLResourceViewPool { MTLResourceId SetTextureViewFromBuffer (IMTLBuffer buffer, MTLTextureDescriptor descriptor, nuint offset, nuint bytesPerRow, nuint index); } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4AccelerationStructureGeometryDescriptor))] interface MTL4AccelerationStructureBoundingBoxGeometryDescriptor { @@ -8795,6 +8832,8 @@ interface MTL4AccelerationStructureBoundingBoxGeometryDescriptor { nuint BoundingBoxCount { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4AccelerationStructureGeometryDescriptor))] interface MTL4AccelerationStructureCurveGeometryDescriptor { @@ -8841,11 +8880,15 @@ interface MTL4AccelerationStructureCurveGeometryDescriptor { MTLCurveEndCaps CurveEndCaps { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTLAccelerationStructureDescriptor))] interface MTL4AccelerationStructureDescriptor { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4AccelerationStructureGeometryDescriptor : NSCopying { @@ -8871,6 +8914,8 @@ interface MTL4AccelerationStructureGeometryDescriptor : NSCopying { nuint PrimitiveDataElementSize { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4AccelerationStructureGeometryDescriptor))] interface MTL4AccelerationStructureMotionBoundingBoxGeometryDescriptor { @@ -8884,6 +8929,8 @@ interface MTL4AccelerationStructureMotionBoundingBoxGeometryDescriptor { nuint BoundingBoxCount { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4AccelerationStructureGeometryDescriptor))] interface MTL4AccelerationStructureMotionCurveGeometryDescriptor { @@ -8930,6 +8977,8 @@ interface MTL4AccelerationStructureMotionCurveGeometryDescriptor { MTLCurveEndCaps CurveEndCaps { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4AccelerationStructureGeometryDescriptor))] interface MTL4AccelerationStructureMotionTriangleGeometryDescriptor { @@ -8958,6 +9007,8 @@ interface MTL4AccelerationStructureMotionTriangleGeometryDescriptor { MTLMatrixLayout TransformationMatrixLayout { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4AccelerationStructureGeometryDescriptor))] interface MTL4AccelerationStructureTriangleGeometryDescriptor { @@ -8986,6 +9037,8 @@ interface MTL4AccelerationStructureTriangleGeometryDescriptor { MTLMatrixLayout TransformationMatrixLayout { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4ArgumentTableDescriptor : NSCopying { @@ -9008,6 +9061,8 @@ interface MTL4ArgumentTableDescriptor : NSCopying { string Label { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4BinaryFunctionDescriptor : NSCopying { @@ -9021,6 +9076,8 @@ interface MTL4BinaryFunctionDescriptor : NSCopying { MTL4BinaryFunctionOptions Options { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4CommandAllocatorDescriptor : NSCopying { @@ -9028,6 +9085,8 @@ interface MTL4CommandAllocatorDescriptor : NSCopying { string Label { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4CommandBufferOptions : NSCopying { @@ -9035,6 +9094,8 @@ interface MTL4CommandBufferOptions : NSCopying { IMTLLogState LogState { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4CommandQueueDescriptor : NSCopying { @@ -9047,6 +9108,8 @@ interface MTL4CommandQueueDescriptor : NSCopying { delegate void MTL4CommitFeedbackHandler (IMTL4CommitFeedback commitFeedback); + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4CommitOptions { @@ -9054,6 +9117,8 @@ interface MTL4CommitOptions { void AddFeedbackHandler (MTL4CommitFeedbackHandler block); } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4CompilerDescriptor : NSCopying { @@ -9064,6 +9129,8 @@ interface MTL4CompilerDescriptor : NSCopying { IMTL4PipelineDataSetSerializer PipelineDataSetSerializer { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4CompilerTaskOptions : NSCopying { @@ -9071,6 +9138,8 @@ interface MTL4CompilerTaskOptions : NSCopying { IMTL4Archive [] LookupArchives { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4PipelineDescriptor))] interface MTL4ComputePipelineDescriptor { @@ -9099,6 +9168,8 @@ interface MTL4ComputePipelineDescriptor { void Reset (); } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4CounterHeapDescriptor : NSCopying { @@ -9109,11 +9180,15 @@ interface MTL4CounterHeapDescriptor : NSCopying { nuint Count { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4FunctionDescriptor : NSCopying { } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4AccelerationStructureDescriptor))] interface MTL4IndirectInstanceAccelerationStructureDescriptor { @@ -9151,6 +9226,8 @@ interface MTL4IndirectInstanceAccelerationStructureDescriptor { nuint MotionTransformStride { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4AccelerationStructureDescriptor))] interface MTL4InstanceAccelerationStructureDescriptor { @@ -9182,6 +9259,8 @@ interface MTL4InstanceAccelerationStructureDescriptor { nuint MotionTransformStride { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4LibraryDescriptor : NSCopying { @@ -9195,6 +9274,8 @@ interface MTL4LibraryDescriptor : NSCopying { string Name { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4FunctionDescriptor))] interface MTL4LibraryFunctionDescriptor { @@ -9205,6 +9286,8 @@ interface MTL4LibraryFunctionDescriptor { IMTLLibrary Library { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4PipelineDescriptor))] interface MTL4MachineLearningPipelineDescriptor { @@ -9228,6 +9311,8 @@ interface MTL4MachineLearningPipelineDescriptor { void Reset (); } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4MachineLearningPipelineReflection { @@ -9235,6 +9320,8 @@ interface MTL4MachineLearningPipelineReflection { IMTLBinding [] Bindings { get; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4PipelineDescriptor))] interface MTL4MeshRenderPipelineDescriptor { @@ -9317,6 +9404,8 @@ interface MTL4MeshRenderPipelineDescriptor { void Reset (); } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4PipelineDataSetSerializerDescriptor : NSCopying { @@ -9324,6 +9413,8 @@ interface MTL4PipelineDataSetSerializerDescriptor : NSCopying { MTL4PipelineDataSetSerializerConfiguration Configuration { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4PipelineDescriptor : NSCopying { @@ -9335,6 +9426,8 @@ interface MTL4PipelineDescriptor : NSCopying { MTL4PipelineOptions Options { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4PipelineOptions : NSCopying { @@ -9345,6 +9438,8 @@ interface MTL4PipelineOptions : NSCopying { MTL4ShaderReflection ShaderReflection { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4PipelineStageDynamicLinkingDescriptor : NSCopying { @@ -9358,6 +9453,8 @@ interface MTL4PipelineStageDynamicLinkingDescriptor : NSCopying { IMTLDynamicLibrary [] PreloadedLibraries { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4AccelerationStructureDescriptor))] interface MTL4PrimitiveAccelerationStructureDescriptor { @@ -9380,6 +9477,8 @@ interface MTL4PrimitiveAccelerationStructureDescriptor { nuint MotionKeyframeCount { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4RenderPassDescriptor : NSCopying { @@ -9435,6 +9534,8 @@ interface MTL4RenderPassDescriptor : NSCopying { bool SupportColorAttachmentMapping { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4RenderPipelineBinaryFunctionsDescriptor : NSCopying { @@ -9457,6 +9558,8 @@ interface MTL4RenderPipelineBinaryFunctionsDescriptor : NSCopying { void Reset (); } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4RenderPipelineColorAttachmentDescriptor : NSCopying { @@ -9491,6 +9594,8 @@ interface MTL4RenderPipelineColorAttachmentDescriptor : NSCopying { void Reset (); } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4RenderPipelineColorAttachmentDescriptorArray : NSCopying { @@ -9504,6 +9609,8 @@ interface MTL4RenderPipelineColorAttachmentDescriptorArray : NSCopying { void Reset (); } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4PipelineDescriptor))] interface MTL4RenderPipelineDescriptor { @@ -9559,6 +9666,8 @@ interface MTL4RenderPipelineDescriptor { void Reset (); } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4RenderPipelineDynamicLinkingDescriptor : NSCopying { @@ -9578,6 +9687,8 @@ interface MTL4RenderPipelineDynamicLinkingDescriptor : NSCopying { MTL4PipelineStageDynamicLinkingDescriptor MeshLinkingDescriptor { get; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4FunctionDescriptor))] interface MTL4SpecializedFunctionDescriptor { @@ -9591,6 +9702,8 @@ interface MTL4SpecializedFunctionDescriptor { MTLFunctionConstantValues ConstantValues { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTL4StaticLinkingDescriptor : NSCopying { @@ -9604,6 +9717,8 @@ interface MTL4StaticLinkingDescriptor : NSCopying { NSDictionary> Groups { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4FunctionDescriptor))] interface MTL4StitchedFunctionDescriptor { @@ -9614,6 +9729,8 @@ interface MTL4StitchedFunctionDescriptor { MTL4FunctionDescriptor [] FunctionDescriptors { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTL4PipelineDescriptor))] interface MTL4TileRenderPipelineDescriptor { @@ -9656,6 +9773,8 @@ interface MTLFunctionReflection { string UserAnnotation { get; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTLLogicalToPhysicalColorAttachmentMap : NSCopying { @@ -9669,6 +9788,8 @@ interface MTLLogicalToPhysicalColorAttachmentMap : NSCopying { void Reset (); } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTLResourceViewPoolDescriptor : NSCopying { @@ -9679,6 +9800,8 @@ interface MTLResourceViewPoolDescriptor : NSCopying { string Label { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTLTensorDescriptor : NSCopying { @@ -9708,6 +9831,8 @@ interface MTLTensorDescriptor : NSCopying { MTLHazardTrackingMode HazardTrackingMode { get; set; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] [DisableDefaultCtor] // all properties are readonly, and has a non-default ctor @@ -9723,6 +9848,8 @@ interface MTLTensorExtents { nint GetExtent (nuint dimensionIndex); } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (MTLType))] interface MTLTensorReferenceType { @@ -9739,6 +9866,8 @@ interface MTLTensorReferenceType { MTLBindingAccess Access { get; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), TV (26, 0), MacCatalyst (26, 0)] [BaseType (typeof (NSObject))] interface MTLTextureViewDescriptor : NSCopying { diff --git a/src/metalfx.cs b/src/metalfx.cs index 7e1739db57e1..19515be35435 100644 --- a/src/metalfx.cs +++ b/src/metalfx.cs @@ -165,6 +165,8 @@ interface MTLFXTemporalScalerDescriptor : NSCopying { bool SupportsMetal4FX (IMTLDevice device); } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), MacCatalyst (26, 0), TV (26, 0)] [BaseType (typeof (NSObject))] interface MTLFXFrameInterpolatorDescriptor : NSCopying { @@ -405,6 +407,8 @@ interface MTL4FXFrameInterpolator : MTLFXFrameInterpolatorBase { void Encode (IMTL4CommandBuffer commandBuffer); } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [Mac (26, 0), iOS (26, 0), MacCatalyst (26, 0), TV (26, 0)] [BaseType (typeof (NSObject))] interface MTLFXTemporalDenoisedScalerDescriptor : NSCopying { diff --git a/src/videotoolbox.cs b/src/videotoolbox.cs index 85f89d3fadd6..a614b9d9c670 100644 --- a/src/videotoolbox.cs +++ b/src/videotoolbox.cs @@ -3127,6 +3127,8 @@ interface VTOpticalFlowParameters : VTFrameProcessorParameters { VTFrameProcessorOpticalFlow DestinationOpticalFlow { get; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [TV (26, 0), MacCatalyst (26, 0), Mac (26, 0), iOS (26, 0)] [BaseType (typeof (NSObject))] [DisableDefaultCtor] @@ -3275,6 +3277,8 @@ interface VTSuperResolutionScalerParameters : VTFrameProcessorParameters { VTSuperResolutionScalerParametersSubmissionMode SubmissionMode { get; } } + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] [MacCatalyst (26, 0), NoTV, Mac (26, 0), iOS (26, 0)] [BaseType (typeof (NSObject))] [DisableDefaultCtor] diff --git a/tests/bgen/BGenTests.cs b/tests/bgen/BGenTests.cs index 7a7ad37fb471..37de37291334 100644 --- a/tests/bgen/BGenTests.cs +++ b/tests/bgen/BGenTests.cs @@ -1657,5 +1657,72 @@ public void BothProtectedAndInternal (Profile profile) var bgen = BuildFile (profile, "both-protected-and-internal.cs"); bgen.AssertNoWarnings (); } + + [Test] + [TestCase (Profile.iOS)] + [TestCase (Profile.tvOS)] + public void SimulatorAvailabilityAttributes (Profile profile) + { + Configuration.IgnoreIfIgnoredPlatform (profile.AsPlatform ()); + var bgen = BuildFile (profile, "simulator-availability-attributes.cs"); + bgen.AssertNoWarnings (); + + var module = bgen.ApiAssembly.MainModule; + + // Verify [UnsupportedSimulator] is copied for the current platform + var unsupportedAll = module.GetType ("NS", "UnsupportedOnAllSimulators"); + var unsupportedAttrs = unsupportedAll.CustomAttributes + .Where (a => a.AttributeType.Name == "UnsupportedSimulatorAttribute") + .ToArray (); + Assert.That (unsupportedAttrs.Length, Is.EqualTo (1), "UnsupportedOnAllSimulators: one attribute for current platform"); + var platformName = (string) unsupportedAttrs [0].ConstructorArguments [0].Value; + var expectedPlatform = profile == Profile.iOS ? "ios" : "tvos"; + Assert.That (platformName, Is.EqualTo (expectedPlatform), "UnsupportedOnAllSimulators platform name"); + + // Verify only the current platform's attribute is emitted + var iosOnly = module.GetType ("NS", "UnsupportedOnIosSimulatorOnly"); + var iosOnlyAttrs = iosOnly.CustomAttributes + .Where (a => a.AttributeType.Name == "UnsupportedSimulatorAttribute") + .ToArray (); + if (profile == Profile.iOS) + Assert.That (iosOnlyAttrs.Length, Is.EqualTo (1), "UnsupportedOnIosSimulatorOnly: present for iOS"); + else + Assert.That (iosOnlyAttrs.Length, Is.EqualTo (0), "UnsupportedOnIosSimulatorOnly: absent for tvOS"); + + // Verify [SupportedSimulator] with version is copied + var supported = module.GetType ("NS", "SupportedOnSimulatorFromVersion"); + var supportedAttrs = supported.CustomAttributes + .Where (a => a.AttributeType.Name == "SupportedSimulatorAttribute") + .ToArray (); + Assert.That (supportedAttrs.Length, Is.EqualTo (1), "SupportedOnSimulatorFromVersion: one attribute"); + var expectedVersion = profile == Profile.iOS ? "ios17.0" : "tvos17.0"; + Assert.That ((string) supportedAttrs [0].ConstructorArguments [0].Value, Is.EqualTo (expectedVersion), "SupportedOnSimulatorFromVersion platform name"); + + // Verify no simulator attributes when none are specified + var noAttrs = module.GetType ("NS", "NoSimulatorAttributes"); + var simulatorAttrs = noAttrs.CustomAttributes + .Where (a => a.AttributeType.Name == "UnsupportedSimulatorAttribute" || a.AttributeType.Name == "SupportedSimulatorAttribute") + .ToArray (); + Assert.That (simulatorAttrs.Length, Is.EqualTo (0), "NoSimulatorAttributes: no simulator attributes"); + } + + [Test] + [TestCase (Profile.macOSMobile)] + [TestCase (Profile.MacCatalyst)] + public void SimulatorAvailabilityAttributes_NotEmittedForMacPlatforms (Profile profile) + { + Configuration.IgnoreIfIgnoredPlatform (profile.AsPlatform ()); + var bgen = BuildFile (profile, "simulator-availability-attributes.cs"); + bgen.AssertNoWarnings (); + + var module = bgen.ApiAssembly.MainModule; + foreach (var typeName in new [] { "UnsupportedOnAllSimulators", "UnsupportedOnIosSimulatorOnly", "SupportedOnSimulatorFromVersion", "NoSimulatorAttributes" }) { + var type = module.GetType ("NS", typeName); + var simulatorAttrs = type.CustomAttributes + .Where (a => a.AttributeType.Name == "UnsupportedSimulatorAttribute" || a.AttributeType.Name == "SupportedSimulatorAttribute") + .ToArray (); + Assert.That (simulatorAttrs.Length, Is.EqualTo (0), $"{typeName}: no simulator attributes on Mac platforms"); + } + } } } diff --git a/tests/bgen/tests/simulator-availability-attributes.cs b/tests/bgen/tests/simulator-availability-attributes.cs new file mode 100644 index 000000000000..1cce845e0b80 --- /dev/null +++ b/tests/bgen/tests/simulator-availability-attributes.cs @@ -0,0 +1,30 @@ +using System; +using Foundation; +using ObjCRuntime; + +namespace NS { + [UnsupportedSimulator ("ios")] + [UnsupportedSimulator ("tvos")] + [iOS (16, 0), TV (16, 0), Mac (13, 0), MacCatalyst (16, 0)] + [BaseType (typeof (NSObject))] + interface UnsupportedOnAllSimulators { + } + + [UnsupportedSimulator ("ios")] + [iOS (16, 0), TV (16, 0), Mac (13, 0), MacCatalyst (16, 0)] + [BaseType (typeof (NSObject))] + interface UnsupportedOnIosSimulatorOnly { + } + + [SupportedSimulator ("ios17.0")] + [SupportedSimulator ("tvos17.0")] + [iOS (16, 0), TV (16, 0), Mac (13, 0), MacCatalyst (16, 0)] + [BaseType (typeof (NSObject))] + interface SupportedOnSimulatorFromVersion { + } + + [iOS (16, 0), TV (16, 0), Mac (13, 0), MacCatalyst (16, 0)] + [BaseType (typeof (NSObject))] + interface NoSimulatorAttributes { + } +} diff --git a/tests/common/PlatformInfo.cs b/tests/common/PlatformInfo.cs index f88e7abd8b46..5e1df0de655a 100644 --- a/tests/common/PlatformInfo.cs +++ b/tests/common/PlatformInfo.cs @@ -98,6 +98,52 @@ public static bool IsAvailableOnHostPlatform (this ICustomAttributeProvider attr return attributeProvider.IsAvailable (PlatformInfo.Host); } + public static bool IsAvailableInSimulator (this ICustomAttributeProvider attributeProvider) + { + if (!TestRuntime.IsSimulator) + return true; + + var customAttributes = attributeProvider.GetCustomAttributes (true); + + string platformPrefix; + switch (PlatformInfo.Host.Name) { + case ApplePlatform.iOS: + platformPrefix = "ios"; + break; + case ApplePlatform.TVOS: + platformPrefix = "tvos"; + break; + default: + return true; + } + + // Check for [UnsupportedSimulator] attributes + foreach (var attr in customAttributes.OfType ()) { + if (attr.PlatformName.StartsWith (platformPrefix, StringComparison.OrdinalIgnoreCase)) + return false; + } + + // Check for [SupportedSimulator] attributes + var supportedAttrs = customAttributes.OfType () + .Where (a => a.PlatformName.StartsWith (platformPrefix, StringComparison.OrdinalIgnoreCase)) + .ToArray (); + + // If no SupportedSimulator attributes for the current platform, assume available + if (supportedAttrs.Length == 0) + return true; + + // There's a SupportedSimulator attribute for the current platform - check version + foreach (var attr in supportedAttrs) { + var versionString = attr.PlatformName.Substring (platformPrefix.Length); + if (string.IsNullOrEmpty (versionString)) + return true; // supported, no version constraint + if (Version.TryParse (versionString, out var version) && PlatformInfo.Host.Version >= version) + return true; + } + + return false; + } + [UnconditionalSuppressMessage ("Trimming", "IL2045", Justification = "Some of the attributes this method uses may have been linked away, so things might not work. It actually works though, so unless something changes, we're going to assume it's trimmer-compatible.")] public static bool IsAvailable (this ICustomAttributeProvider attributeProvider, PlatformInfo targetPlatform) { diff --git a/tests/introspection/ApiBaseTest.cs b/tests/introspection/ApiBaseTest.cs index 02cd37df96cd..849b7481600b 100644 --- a/tests/introspection/ApiBaseTest.cs +++ b/tests/introspection/ApiBaseTest.cs @@ -158,6 +158,7 @@ protected virtual bool SkipDueToAttribute (MemberInfo? member) return false; return !member.IsAvailableOnHostPlatform () || + !member.IsAvailableInSimulator () || SkipDueToAttribute (member.DeclaringType) || SkipDueToAttributeInProperty (member); } diff --git a/tests/introspection/ApiCtorInitTest.cs b/tests/introspection/ApiCtorInitTest.cs index 53a9b0d0ca5c..781554ea0cbd 100644 --- a/tests/introspection/ApiCtorInitTest.cs +++ b/tests/introspection/ApiCtorInitTest.cs @@ -176,52 +176,7 @@ protected virtual bool Skip (Type type) #endif case "PhaseConeDirectivityModelParameters": return !TestRuntime.IsSimulator; // fails on device - case "MTL4AccelerationStructureBoundingBoxGeometryDescriptor": - case "MTL4AccelerationStructureCurveGeometryDescriptor": - case "MTL4AccelerationStructureDescriptor": - case "MTL4AccelerationStructureGeometryDescriptor": - case "MTL4AccelerationStructureMotionBoundingBoxGeometryDescriptor": - case "MTL4AccelerationStructureMotionCurveGeometryDescriptor": - case "MTL4AccelerationStructureMotionTriangleGeometryDescriptor": - case "MTL4AccelerationStructureTriangleGeometryDescriptor": - case "MTL4ArgumentTableDescriptor": - case "MTL4BinaryFunctionDescriptor": - case "MTL4CommandAllocatorDescriptor": - case "MTL4CommandBufferOptions": - case "MTL4CommandQueueDescriptor": - case "MTL4CommitOptions": - case "MTL4CompilerDescriptor": - case "MTL4CompilerTaskOptions": - case "MTL4ComputePipelineDescriptor": - case "MTL4CounterHeapDescriptor": - case "MTL4FunctionDescriptor": - case "MTL4IndirectInstanceAccelerationStructureDescriptor": - case "MTL4InstanceAccelerationStructureDescriptor": - case "MTL4LibraryDescriptor": - case "MTL4LibraryFunctionDescriptor": - case "MTL4MachineLearningPipelineDescriptor": - case "MTL4MachineLearningPipelineReflection": - case "MTL4MeshRenderPipelineDescriptor": - case "MTL4PipelineDataSetSerializerDescriptor": - case "MTL4PipelineDescriptor": - case "MTL4PipelineOptions": - case "MTL4PipelineStageDynamicLinkingDescriptor": - case "MTL4PrimitiveAccelerationStructureDescriptor": - case "MTL4RenderPassDescriptor": - case "MTL4RenderPipelineBinaryFunctionsDescriptor": - case "MTL4RenderPipelineColorAttachmentDescriptor": - case "MTL4RenderPipelineColorAttachmentDescriptorArray": - case "MTL4RenderPipelineDescriptor": - case "MTL4RenderPipelineDynamicLinkingDescriptor": - case "MTL4SpecializedFunctionDescriptor": - case "MTL4StaticLinkingDescriptor": - case "MTL4StitchedFunctionDescriptor": - case "MTL4TileRenderPipelineDescriptor": - case "MTLLogicalToPhysicalColorAttachmentMap": - case "MTLResourceViewPoolDescriptor": - case "MTLTensorDescriptor": case "MTLTensorReferenceType": - case "MTLTextureViewDescriptor": case "MTLFXFrameInterpolatorDescriptor": case "MTLFXTemporalDenoisedScalerDescriptor": if (TestRuntime.IsSimulator) From d8423bacc8ab0a0e9eb7bcfc5a34f967ad6adf89 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 6 May 2026 12:38:38 +0200 Subject: [PATCH 2/4] dlfcn inlining: use simulator supportedness attributes --- tools/common/DerivedLinkContext.cs | 71 +++++++++++++++++++ .../Steps/InlineDlfcnMethodsStep.cs | 12 ++++ 2 files changed, 83 insertions(+) diff --git a/tools/common/DerivedLinkContext.cs b/tools/common/DerivedLinkContext.cs index 8e679a69de81..da5f8090e812 100644 --- a/tools/common/DerivedLinkContext.cs +++ b/tools/common/DerivedLinkContext.cs @@ -9,6 +9,7 @@ using Mono.Tuner; using Xamarin.Bundler; using Xamarin.Linker; +using Xamarin.Utils; #if !LEGACY_TOOLS using LinkContext = Xamarin.Bundler.DotNetLinkContext; @@ -235,6 +236,76 @@ public void AddLinkedAwayType (TypeDefinition td) return null; } + public bool HasAvailabilityAttributesShowingUnavailableInSimulator (ICustomAttributeProvider type, MethodDefinition? methodForErrorReporting = null) + { + if (!App.IsSimulatorBuild) + throw ErrorHelper.CreateError (99, "HasAvailabilityAttributesShowingUnavailableInSimulator should not be called when not building for the simulator. Please file an issue at https://github.com/dotnet/macios/issues."); + + if (!type.HasCustomAttributes) + return true; // no attributes so say otherwise, so available + + foreach (var attrib in type.CustomAttributes) { + if (attrib.AttributeType.Is ("ObjCRuntime", "UnsupportedSimulatorAttribute")) { + if (attrib.ConstructorArguments.Count == 1 && attrib.ConstructorArguments [0].Value is string reason) { + switch (App.Platform) { + case ApplePlatform.iOS: + if (string.Equals (reason, "ios", StringComparison.OrdinalIgnoreCase)) + return true; + continue; + case ApplePlatform.TVOS: + if (string.Equals (reason, "tvos", StringComparison.OrdinalIgnoreCase)) + return true; + continue; + default: + LinkerConfiguration.Report (LinkerConfiguration.Context, ErrorHelper.CreateWarning (App, 99, methodForErrorReporting, "Unexpected platform '{0}'. Please file an issue at https://github.com/dotnet/macios/issues.", App.Platform)); + continue; + }; + } + LinkerConfiguration.Report (LinkerConfiguration.Context,ErrorHelper.CreateWarning (App, 9999, methodForErrorReporting, "'{0}' is marked with a malformed attribute: {1}. Please file an issue at https://github.com/dotnet/macios/issues.", type.AsString (), attrib.RenderAttribute ())); + continue; + } + + if (attrib.AttributeType.Is ("ObjCRuntime", "SupportedSimulatorAttribute")) { + if (attrib.ConstructorArguments.Count == 1 && attrib.ConstructorArguments [0].Value is string reason) { + string os = ""; + switch (App.Platform) { + case ApplePlatform.iOS: + if (!reason.StartsWith ("ios", StringComparison.OrdinalIgnoreCase)) + continue; + os = reason.Substring (3); + break; + case ApplePlatform.TVOS: + if (!reason.StartsWith ("tvos", StringComparison.OrdinalIgnoreCase)) + continue; + os = reason.Substring (4); + break; + default: + LinkerConfiguration.Report (LinkerConfiguration.Context, ErrorHelper.CreateWarning (App, 99, methodForErrorReporting, "Unexpected platform '{0}'. Please file an issue at https://github.com/dotnet/macios/issues.", App.Platform)); + continue; + } + + if (string.IsNullOrEmpty (os)) { + // empty version: always available in the simulator + } else if (Version.TryParse (os, out var version)) { + var simulatorVersion = App.DeploymentTarget; + if (simulatorVersion is null) { + LinkerConfiguration.Report (LinkerConfiguration.Context, ErrorHelper.CreateWarning (App, 99, methodForErrorReporting, "No deployment target available. Please file an issue at https://github.com/dotnet/macios/issues.")); + } else if (version > simulatorVersion) { + return true; + } + } else { + LinkerConfiguration.Report (LinkerConfiguration.Context,ErrorHelper.CreateWarning (App, 9999, methodForErrorReporting, "'{0}' is marked with a malformed attribute (invalid version): {1}. Please file an issue at https://github.com/dotnet/macios/issues.", type.AsString (), attrib.RenderAttribute ())); + } + continue; + } + LinkerConfiguration.Report (LinkerConfiguration.Context,ErrorHelper.CreateWarning (App, 9999, methodForErrorReporting, "'{0}' is marked with a malformed attribute: {1}. Please file an issue at https://github.com/dotnet/macios/issues.", type.AsString (), attrib.RenderAttribute ())); + continue; + } + } + + return false; + } + #if !LEGACY_TOOLS class AttributeStorage : ICustomAttribute { public CustomAttribute Attribute { get; } diff --git a/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs b/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs index 0012ca7a456f..80ab202f1c9c 100644 --- a/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs +++ b/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs @@ -539,6 +539,18 @@ protected override bool ProcessMethod (MethodDefinition method) if (method.DeclaringType.Name == "Dlfcn" && method.DeclaringType.Namespace == "ObjCRuntime") return modified; // don't process the Dlfcn methods themselves + if (DerivedLinkContext.App.IsSimulatorBuild) { + // if the method or its declaring type aren't available in the simulator, and we're building for the simulator, then don't inline. + if (DerivedLinkContext.HasAvailabilityAttributesShowingUnavailableInSimulator (method, method)) { + Driver.Log (3, $"Method {method.FullName} is not available in the simulator. Skipping inlining Dlfcn calls for this method."); + return modified; + } + if (DerivedLinkContext.HasAvailabilityAttributesShowingUnavailableInSimulator (method.DeclaringType, method)) { + Driver.Log (3, $"Type {method.DeclaringType.FullName} is not available in the simulator. Skipping inlining Dlfcn calls for this method."); + return modified; + } + } + foreach (var instr in method.Body.Instructions) { if (instr.Operand is not MethodReference mr) continue; From 34c7a930f70805608b3e07f6906ee67c4331b408 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 17 Mar 2026 12:26:29 +0100 Subject: [PATCH 3/4] [dotnet-linker] Add a trimmer step to inline calls to Class.GetHandle[Intrinsic]. --- docs/code/class-handles.md | 57 ++++++ dotnet/targets/Xamarin.Shared.Sdk.props | 6 + dotnet/targets/Xamarin.Shared.Sdk.targets | 27 ++- .../Tasks/CollectPostILTrimInformation.cs | 73 ++++--- .../Tasks/CollectUnresolvedNativeSymbols.cs | 2 +- .../ComputeNativeAOTSurvivingNativeSymbols.cs | 41 +--- .../Tasks/PostTrimmingProcessing.cs | 152 ++++++++++++--- src/ObjCRuntime/Registrar.cs | 22 ++- tests/common/test-variations.csproj | 13 ++ .../xharness/Jenkins/TestVariationsFactory.cs | 4 +- tools/common/DerivedLinkContext.cs | 18 +- tools/common/FileUtils.cs | 14 ++ tools/common/PathUtils.cs | 20 ++ tools/common/StaticRegistrar.cs | 2 +- tools/dotnet-linker/AppBundleRewriter.cs | 50 +++++ tools/dotnet-linker/LinkerConfiguration.cs | 24 ++- .../Steps/InlineClassGetHandleStep.cs | 184 ++++++++++++++++++ .../Steps/InlineDlfcnMethodsStep.cs | 38 +--- .../MonoTouch.Tuner/ListExportedSymbols.cs | 40 ++-- tools/mtouch/Errors.designer.cs | 45 +++++ tools/mtouch/Errors.resx | 20 ++ 21 files changed, 706 insertions(+), 146 deletions(-) create mode 100644 docs/code/class-handles.md create mode 100644 tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs diff --git a/docs/code/class-handles.md b/docs/code/class-handles.md new file mode 100644 index 000000000000..48255fb5aa6a --- /dev/null +++ b/docs/code/class-handles.md @@ -0,0 +1,57 @@ +# Objective-C classes + +Objective-C classes can be referenced from managed code in several ways: + +* Calls to Class.GetHandle / GetHandleIntrinsic + +It's highly desirable to use a direct native reference to Objective-C classes when building a mobile app, for a few reasons: + +* It's faster at runtime, and the app is smaller. +* If the referenced Objective-C class comes from a third-party static library, the + native linker can remove it if it's configured to remove unused code + (because the native linker can't see that the class is in fact used + at runtime) unless there's a direct native reference to the class. + +On the other hand there's one scenario when a direct native reference is not desirable: when the native Objective-C class does not exist. + +In order to create a direct native reference to Objective-C classes, we need to know the names of those Objective-C classes. + +## The `InlineClassGetHandle` property + +This behavior is controlled by the `InlineClassGetHandle` MSBuild property, which +can either be enabled or disabled. + +See the [build properties documentation](../building-apps/build-properties.md) for default values. + +## How it works + +During the build we try to collect the following: + +* Any calls to `Class.GetHandle[Intrinsic]` APIs: we try to collect the class name (this might not always succeed, if the class name is not a constant). + +This is further complicated by the fact that we only want to create native +references for managed references that survive trimming. + +So we do the following: + +1. During trimming, two custom linker steps execute: + + * `InlineClassGetHandleStep`: for every call `Class.GetHandle` we've + collected, this step creates a P/Invoke to a native method that will + return the Objective-C class for that symbol (using a direct native + reference), and modifies the code that fetches that symbol to call said + P/Invoke. + +2. After trimming, we figure out which of those symbols survived: + + * For ILTrim: the `_CollectPostILTrimInformation` MSBuild target inspects + the trimmed assemblies and collects all the inlined P/Invokes that + survived. Per-assembly results are cached to speed up incremental builds. + * For NativeAOT: the `_CollectPostNativeAOTTrimInformation` MSBuild target + inspects the native object file (or static library) produced by NativeAOT, + collects all unresolved native references, and filters them against the + Objective-C classes to determine which survived. + +3. The `_PostTrimmingProcessing` MSBuild target takes the surviving symbols + from either path, generates the corresponding native Objective-C code, and + adds it to the list of files to compile and link into the final executable. diff --git a/dotnet/targets/Xamarin.Shared.Sdk.props b/dotnet/targets/Xamarin.Shared.Sdk.props index 40b953914bac..532e7e2a44cb 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.props +++ b/dotnet/targets/Xamarin.Shared.Sdk.props @@ -107,6 +107,12 @@ compatibility + + + strict + compatibility + + diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index d1f37e5aceba..37ff08101331 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -642,6 +642,7 @@ @(_BundlerEnvironmentVariables -> 'EnvironmentVariable=Overwrite=%(Overwrite)|%(Identity)=%(Value)') @(_XamarinFrameworkAssemblies -> 'FrameworkAssembly=%(Filename)') Interpreter=$(MtouchInterpreter) + InlineClassGetHandle=$(InlineClassGetHandle) InlineDlfcnMethods=$(InlineDlfcnMethods) IntermediateLinkDir=$(IntermediateLinkDir) IntermediateOutputPath=$(DeviceSpecificIntermediateOutputPath) @@ -674,6 +675,7 @@ TargetArchitectures=$(TargetArchitectures) TargetFramework=$(_ComputedTargetFrameworkMoniker) TypeMapAssemblyName=$(_TypeMapAssemblyName) + TypeMapFilePath=$(_TypeMapFilePath) TypeMapOutputDirectory=$(_TypeMapOutputDirectory) UseLlvm=$(MtouchUseLlvm) Verbosity=$(_BundlerVerbosity) @@ -798,6 +800,7 @@ <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PreMarkDispatcher" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.ManagedRegistrarStep" Condition="'$(Registrar)' == 'managed-static' Or '$(Registrar)' == 'trimmable-static'" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.TrimmableRegistrarStep" Condition="'$(Registrar)' == 'trimmable-static'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.InlineClassGetHandleStep" Condition="'$(InlineClassGetHandle)' != ''" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="OutputStep" Type="Xamarin.Linker.Steps.ListExportedSymbols" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="OutputStep" Type="Xamarin.Linker.Steps.ListExportedSymbols" Condition="'$(InlineClassGetHandle)' == '' Or '$(InlineDlfcnMethods)' == ''"/> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="OutputStep" Type="Xamarin.Linker.Steps.PreOutputDispatcher" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="OutputStep" Type="Xamarin.Linker.ClassHandleRewriterStep" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="OutputStep" Type="Xamarin.Linker.ClassHandleRewriterStep" Condition="'$(InlineClassGetHandle)' == ''" /> + <_TypeMapFilePath Condition="'$(_TypeMapFilePath)' == ''">$(DeviceSpecificIntermediateOutputPath)type-map.txt @@ -1688,20 +1694,22 @@ <_ILTrimSurvivingNativeSymbolsFile>$(DeviceSpecificIntermediateOutputPath)inlined-dlfcn\iltrim-surviving-native-symbols.txt - <_NativeAOTUnresolvedSymbolsFile>$(DeviceSpecificIntermediateOutputPath)nativeaot-unresolved-symbols.txt <_NativeAOTSurvivingNativeSymbolsFile>$(DeviceSpecificIntermediateOutputPath)nativeaot-surviving-native-symbols.txt + <_ILTrimSurvivingClassesFile>$(DeviceSpecificIntermediateOutputPath)inlined-class-gethandle\iltrim-classes.txt + <_NativeAOTSurvivingClassesFile>$(DeviceSpecificIntermediateOutputPath)inlined-class-gethandle\nativeaot-classes.txt @@ -1720,12 +1728,17 @@ <_SurvivingNativeSymbolsFile Include="$(_ILTrimSurvivingNativeSymbolsFile)" Condition="Exists('$(_ILTrimSurvivingNativeSymbolsFile)')" /> <_SurvivingNativeSymbolsFile Include="$(_NativeAOTSurvivingNativeSymbolsFile)" Condition="Exists('$(_NativeAOTSurvivingNativeSymbolsFile)')" /> + + <_SurvivingClassesFiles Include="$(_ILTrimSurvivingClassesFile)" Condition="Exists('$(_ILTrimSurvivingClassesFile)')" /> + <_SurvivingClassesFiles Include="$(_NativeAOTSurvivingClassesFile)" Condition="Exists('$(_NativeAOTSurvivingClassesFile)')" /> @@ -1829,6 +1842,9 @@ Inputs="$(NativeObject)" Outputs="$(_NativeAOTSurvivingNativeSymbolsFile)" > + + <_NativeAOTUnresolvedSymbolsFile>$(DeviceSpecificIntermediateOutputPath)nativeaot-unresolved-symbols.txt + diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectPostILTrimInformation.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectPostILTrimInformation.cs index 116ce430cb49..f852f13b4e6a 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectPostILTrimInformation.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/CollectPostILTrimInformation.cs @@ -9,12 +9,14 @@ using Mono.Cecil; +using Xamarin.Utils; + #nullable enable namespace Xamarin.MacDev.Tasks { /// /// Scans trimmed assemblies to collect information that survived trimming. - /// See docs/code/native-symbols.md for an overview of native symbol handling. + /// See docs/code/native-symbols.md and docs/code/class-handles.md for an overview of native symbol handling. /// public class CollectPostILTrimInformation : XamarinTask { [Required] @@ -26,6 +28,12 @@ public class CollectPostILTrimInformation : XamarinTask { [Required] public string SurvivingNativeSymbolsFile { get; set; } = ""; + /// + /// Output file listing the Class.GetHandle calls that survived trimming. + /// + [Required] + public string SurvivingClassesFile { get; set; } = ""; + /// /// Directory for per-assembly cache files, to avoid re-scanning unchanged assemblies. /// @@ -51,17 +59,17 @@ void CollectSurvivingNativeSymbols () continue; var assemblyName = Path.GetFileNameWithoutExtension (assemblyPath); - var cacheFile = Path.Combine (CacheDirectory, assemblyName + ".dlfcn-symbols.cache"); + var cacheFile = Path.Combine (CacheDirectory, assemblyName + ".internal-symbols.cache"); string []? cachedSymbols = null; if (File.Exists (cacheFile) && File.GetLastWriteTimeUtc (cacheFile) >= File.GetLastWriteTimeUtc (assemblyPath)) { cachedSymbols = File.ReadAllLines (cacheFile); - Log.LogMessage (MessageImportance.Low, "Using cached dlfcn symbols for {0}", assemblyName); + Log.LogMessage (MessageImportance.Low, "Using cached internal symbols for {0}", assemblyName); survivingSymbols.UnionWith (cachedSymbols); } else { var assemblySymbols = new HashSet (); - CollectDlfcnSymbolsFromAssembly (assemblyPath, assemblySymbols); + CollectInternalSymbolsFromAssembly (assemblyPath, assemblySymbols); // Write per-assembly cache (sorted for stability). var sortedAssemblySymbols = assemblySymbols.OrderBy (s => s).ToArray (); @@ -71,29 +79,53 @@ void CollectSurvivingNativeSymbols () } } + WriteSymbolsToFile (this, SurvivingNativeSymbolsFile, FilterToDlfcnSymbols (survivingSymbols)); + WriteSymbolsToFile (this, SurvivingClassesFile, FilterToClassSymbols (survivingSymbols)); + } + + public static void WriteSymbolsToFile (XamarinTask task, string file, IEnumerable unsortedSymbols) + { // Write the combined results only if contents changed (sorted for stability). - var sorted = survivingSymbols.OrderBy (s => s).ToArray (); + var sorted = unsortedSymbols.OrderBy (s => s).ToArray (); - if (File.Exists (SurvivingNativeSymbolsFile)) { - var existing = File.ReadAllLines (SurvivingNativeSymbolsFile); - if (existing.SequenceEqual (sorted)) + if (File.Exists (file)) { + var existing = File.ReadAllLines (file); + if (existing.SequenceEqual (sorted)) { + task.Log.LogMessage (MessageImportance.Low, "The file {0} is already up-to-date with {1} symbols", file, sorted.Length); return; + } } - var dir = Path.GetDirectoryName (SurvivingNativeSymbolsFile); - if (!string.IsNullOrEmpty (dir)) - Directory.CreateDirectory (dir); - File.WriteAllLines (SurvivingNativeSymbolsFile, sorted); - Log.LogMessage (MessageImportance.Low, "Found {0} surviving inlined dlfcn symbols", survivingSymbols.Count); + PathUtils.CreateDirectoryForFile (file); + File.WriteAllLines (file, sorted); + task.Log.LogMessage (MessageImportance.Low, "Wrote {0} symbols to {1}", sorted.Length, file); } - static void CollectDlfcnSymbolsFromAssembly (string assemblyPath, HashSet survivingSymbols) + public static IEnumerable FilterToDlfcnSymbols (IEnumerable symbols) { - const string prefix = "xamarin_Dlfcn_"; - const string suffix = "_Native"; + return FilterTo (symbols, "xamarin_Dlfcn_", "_Native"); + } + public static IEnumerable FilterToClassSymbols (IEnumerable symbols) + { + return FilterTo (symbols, "xamarin_Class_GetHandle_", "_Native"); + } + + static IEnumerable FilterTo (IEnumerable symbols, string prefix, string suffix) + { + return symbols + .Where (symbol => symbol.StartsWith (prefix, StringComparison.Ordinal) && symbol.EndsWith (suffix, StringComparison.Ordinal)) + .Select (symbol => symbol.Substring (prefix.Length, symbol.Length - prefix.Length - suffix.Length)); + } + + static void CollectInternalSymbolsFromAssembly (string assemblyPath, HashSet survivingSymbols) + { using var assembly = AssemblyDefinition.ReadAssembly (assemblyPath, new ReaderParameters { ReadSymbols = false }); foreach (var module in assembly.Modules) { + if (!module.HasModuleReferences) + continue; + if (!module.ModuleReferences.Any (mr => mr.Name == "__Internal")) + continue; foreach (var type in module.Types) { if (!type.HasMethods) continue; @@ -102,14 +134,7 @@ static void CollectDlfcnSymbolsFromAssembly (string assemblyPath, HashSet public class ComputeNativeAOTSurvivingNativeSymbols : XamarinTask { /// @@ -29,39 +29,18 @@ public class ComputeNativeAOTSurvivingNativeSymbols : XamarinTask { [Required] public string SurvivingNativeSymbolsFile { get; set; } = ""; + /// + /// Output file listing the Class.GetHandle calls that survived trimming. + /// + [Required] + public string SurvivingClassesFile { get; set; } = ""; + public override bool Execute () { - if (!File.Exists (UnresolvedSymbolsFile)) - return !Log.HasLoggedErrors; - - const string prefix = "_xamarin_Dlfcn_"; - const string suffix = "_Native"; - var survivingSymbols = new HashSet (); - - foreach (var sym in File.ReadAllLines (UnresolvedSymbolsFile)) { - if (!sym.StartsWith (prefix) || !sym.EndsWith (suffix)) - continue; - var symbolLength = sym.Length - prefix.Length - suffix.Length; - if (symbolLength <= 0) - continue; - var symbolName = sym.Substring (prefix.Length, symbolLength); - survivingSymbols.Add (symbolName); - } - - var sorted = survivingSymbols.OrderBy (s => s).ToArray (); - - if (File.Exists (SurvivingNativeSymbolsFile)) { - var existing = File.ReadAllLines (SurvivingNativeSymbolsFile); - if (existing.SequenceEqual (sorted)) - return !Log.HasLoggedErrors; - } - - var dir = Path.GetDirectoryName (SurvivingNativeSymbolsFile); - if (!string.IsNullOrEmpty (dir)) - Directory.CreateDirectory (dir); - File.WriteAllLines (SurvivingNativeSymbolsFile, sorted); - Log.LogMessage (MessageImportance.Low, "Found {0} surviving native symbols from NativeAOT", survivingSymbols.Count); + var unresolvedSymbols = File.ReadAllLines (UnresolvedSymbolsFile); + CollectPostILTrimInformation.WriteSymbolsToFile (this, SurvivingNativeSymbolsFile, CollectPostILTrimInformation.FilterToDlfcnSymbols (unresolvedSymbols)); + CollectPostILTrimInformation.WriteSymbolsToFile (this, SurvivingClassesFile, CollectPostILTrimInformation.FilterToClassSymbols (unresolvedSymbols)); return !Log.HasLoggedErrors; } } diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/PostTrimmingProcessing.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/PostTrimmingProcessing.cs index 77705a8dd1d7..8bfaccab0aa4 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/PostTrimmingProcessing.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/PostTrimmingProcessing.cs @@ -9,13 +9,14 @@ using Microsoft.Build.Framework; using Xamarin.Bundler; +using Xamarin.Utils; #nullable enable namespace Xamarin.MacDev.Tasks { /// /// Performs post-trimming processing, generating native code only for symbols that survived trimming. - /// See docs/code/native-symbols.md for an overview of native symbol handling. + /// See docs/code/native-symbols.md and docs/code/class-handles.md for an overview of native symbol handling. /// public class PostTrimmingProcessing : XamarinTask { [Required] @@ -26,6 +27,13 @@ public class PostTrimmingProcessing : XamarinTask { public ITaskItem [] ReferenceNativeSymbol { get; set; } = []; + /// + /// Files listing calls to Class.GetHandle that survived trimming. Each file contains one symbol name per line. + /// These can come from either ILTrim (CollectPostILTrimInformation) or NativeAOT + /// (ComputeNativeAOTSurvivingNativeSymbols). + /// + public ITaskItem [] SurvivingClassesFiles { get; set; } = []; + /// /// Files listing native symbols that survived trimming. Each file contains one symbol name per line. /// These can come from either ILTrim (CollectPostILTrimInformation) or NativeAOT @@ -33,6 +41,9 @@ public class PostTrimmingProcessing : XamarinTask { /// public ITaskItem [] SurvivingNativeSymbolsFiles { get; set; } = []; + // Type map + public string TypeMapFilePath { get; set; } = ""; + /// /// Output native source files to be compiled and linked. /// @@ -71,41 +82,133 @@ HashSet IgnoredSymbols { public override bool Execute () { - var items = new List (); + var nativeSourceFiles = new List (); - GenerateInlinedDlfcnNativeCode (items); + Directory.CreateDirectory (OutputDirectory); + GenerateInlinedDlfcnNativeCode (nativeSourceFiles); + GenerateInlinedClassGetHandleNativeCode (nativeSourceFiles); + + NativeSourceFiles = nativeSourceFiles.Select (path => { + var item = new Microsoft.Build.Utilities.TaskItem (path); + item.SetMetadata ("Arch", Architecture.ToLowerInvariant ()); + return item; + }).ToArray (); - NativeSourceFiles = items.ToArray (); return !Log.HasLoggedErrors; } - void GenerateInlinedDlfcnNativeCode (List items) + static HashSet ReadUniqueLinesFromFiles (IEnumerable files) { - // Collect all surviving symbols from all input files. - var survivingSymbols = new HashSet (); - foreach (var file in SurvivingNativeSymbolsFiles) { + var lines = new HashSet (); + foreach (var file in files) { var path = file.ItemSpec; if (!File.Exists (path)) continue; - survivingSymbols.UnionWith (File.ReadAllLines (path)); + lines.UnionWith (File.ReadAllLines (path)); } + return lines; + } - var survivingButIgnoredSymbols = survivingSymbols.Intersect (IgnoredSymbols).ToList (); - if (survivingButIgnoredSymbols.Count > 0) { - Log.LogMessage (MessageImportance.Low, "The following symbols survived trimming but are marked as ignored:"); - foreach (var symbol in survivingButIgnoredSymbols) - Log.LogMessage (MessageImportance.Low, " {0}", symbol); - survivingSymbols.ExceptWith (survivingButIgnoredSymbols); + List FilterOutIgnoredSymbols (HashSet survivingSymbols, bool filterObjectiveCClasses) + { + var rv = new HashSet (survivingSymbols); + + foreach (var rns in ReferenceNativeSymbol) { + var nativeSymbol = rns.ItemSpec; + var symbolMode = rns.GetMetadata ("SymbolMode"); + if (!string.Equals (symbolMode, "Ignore", StringComparison.OrdinalIgnoreCase)) + continue; + + var symbolType = rns.GetMetadata ("SymbolType").ToLowerInvariant (); + switch (symbolType) { + case "objectivecclass": + if (filterObjectiveCClasses && rv.Remove (nativeSymbol)) { + Log.LogMessage (MessageImportance.Low, "Ignoring Objective-C class '{0}'", nativeSymbol); + } + break; + case "function": + case "field": + if (!filterObjectiveCClasses && rv.Remove (nativeSymbol)) { + Log.LogMessage (MessageImportance.Low, "Ignoring native symbol '{0}'", nativeSymbol); + } + break; + default: + Log.LogMessage (MessageImportance.Low, "Ignoring symbol '{0}' with unknown SymbolType '{1}'", nativeSymbol, symbolType); + continue; + } } + rv.Remove (""); // no empty symbols + + return rv.OrderBy (v => v).ToList (); + } + + void GenerateInlinedClassGetHandleNativeCode (List items) + { + // Collect all surviving symbols from all input files. + var classes = FilterOutIgnoredSymbols (ReadUniqueLinesFromFiles (SurvivingClassesFiles), filterObjectiveCClasses: true); + + if (classes.Count == 0) { + Log.LogMessage (MessageImportance.Low, "There were no surviving Objective-C classes that require inlined Class.GetHandle native code."); + return; + } + + var typeMap = File.ReadAllLines (TypeMapFilePath) + .Select (line => { + var parts = line.Split ('|'); + string className = ""; + string framework = ""; + string introduced = ""; + foreach (var part in parts) { + var kvp = part.Split (new char [] { '=' }, 2); + if (kvp.Length != 2) + continue; + var key = kvp [0].Trim (); + var value = kvp [1].Trim (); + switch (key) { + case "Class": + className = value; + break; + case "Framework": + framework = value; + break; + case "Introduced": + introduced = value; + break; + } + } + return (Class: className, Framework: framework, Introduced: introduced); + }) + .ToDictionary (v => v.Class); + + var sb = new StringBuilder (); + sb.AppendLine ($"#include "); + sb.AppendLine ($"#include "); + foreach (var objectiveCClassName in classes) { + typeMap.TryGetValue (objectiveCClassName, out var info); + if (info.Framework != "Foundation") { + sb.AppendLine ($"__attribute__((weak_import)) @interface {objectiveCClassName} : NSObject @end"); + } + sb.AppendLine ($"Class xamarin_Class_GetHandle_{objectiveCClassName}_Native ();"); + sb.AppendLine ($"Class xamarin_Class_GetHandle_{objectiveCClassName}_Native () {{ return [{objectiveCClassName} class]; }}"); + sb.AppendLine (); + } + + var outputPath = Path.Combine (OutputDirectory, "inlined-class-gethandle.m"); + FileUtils.WriteIfDifferent (outputPath, sb.ToString (), (msg) => Log.LogMessage (MessageImportance.Low, msg)); + + items.Add (outputPath); + } + + void GenerateInlinedDlfcnNativeCode (List items) + { + var survivingSymbols = FilterOutIgnoredSymbols (ReadUniqueLinesFromFiles (SurvivingNativeSymbolsFiles), filterObjectiveCClasses: false); + if (survivingSymbols.Count == 0) { Log.LogMessage (MessageImportance.Low, "There were no surviving symbols that require inlined dlfcn native code."); return; } - Directory.CreateDirectory (OutputDirectory); - var outputPath = Path.Combine (OutputDirectory, "inlined-dlfcn.c"); - var sb = new StringBuilder (); // The generated C code uses 'extern void*' declarations and returns the address of the symbol. // This is intentional: it allows the native linker to resolve the symbol at link time, which @@ -118,17 +221,10 @@ void GenerateInlinedDlfcnNativeCode (List items) sb.AppendLine (); } - var content = sb.ToString (); - if (File.Exists (outputPath) && File.ReadAllText (outputPath) == content) { - Log.LogMessage (MessageImportance.Low, "Inlined dlfcn native code is up to date"); - } else { - File.WriteAllText (outputPath, content); - Log.LogMessage (MessageImportance.Low, "Generated inlined dlfcn native code with {0} symbols", survivingSymbols.Count); - } + var outputPath = Path.Combine (OutputDirectory, "inlined-dlfcn.c"); + FileUtils.WriteIfDifferent (outputPath, sb.ToString (), (msg) => Log.LogMessage (MessageImportance.Low, msg)); - var item = new Microsoft.Build.Utilities.TaskItem (outputPath); - item.SetMetadata ("Arch", Architecture.ToLowerInvariant ()); - items.Add (item); + items.Add (outputPath); } } } diff --git a/src/ObjCRuntime/Registrar.cs b/src/ObjCRuntime/Registrar.cs index 173bf5645c4d..2225bdb2866c 100644 --- a/src/ObjCRuntime/Registrar.cs +++ b/src/ObjCRuntime/Registrar.cs @@ -1119,7 +1119,7 @@ protected virtual void OnRegisterCategory (ObjCType type, [NotNullIfNotNull (nam protected abstract ConnectAttribute? GetConnectAttribute (TProperty property); // Return null if no attribute is found. Do not consider inherited properties. public abstract ProtocolAttribute? GetProtocolAttribute (TType type); // Return null if no attribute is found. Do not consider base types. protected abstract IEnumerable GetProtocolMemberAttributes (TType type); // Return null if no attributes found. Do not consider base types. - protected virtual Version? GetSdkIntroducedVersion (TType obj, out string? message) { message = null; return null; } // returns the sdk version when the type was introduced for the current platform (null if all supported versions) + public virtual Version? GetSdkIntroducedVersion (TType obj, out string? message) { message = null; return null; } // returns the sdk version when the type was introduced for the current platform (null if all supported versions) protected abstract Version GetSDKVersion (); protected abstract TType? GetProtocolAttributeWrapperType (TType type); // Return null if no attribute is found. Do not consider base types. public abstract BindAsAttribute? GetBindAsAttribute (TMethod method, int parameter_index); // If parameter_index = -1 then get the attribute for the return type. Return null if no attribute is found. Must consider base method. @@ -2639,6 +2639,26 @@ protected string ToSignature (TType type, ObjCMember member, bool forProperty = throw ErrorHelper.CreateError (4101, Errors.MT4101, GetTypeFullName (type)); } + // Gets the Objective-C name for the given type. + // Returns false if the type in question isn't exported to Objective-C, and thus doesn't have an Objective-C name. + public bool TryGetExportedTypeName (TType type, [NotNullWhen (true)] out string? name) + { + name = null; + + var registerAttribute = GetRegisterAttribute (type); + if (registerAttribute is null) + return false; + + if (!registerAttribute.IsWrapper) + return false; + + if (HasProtocolAttribute (type)) + return false; + + name = GetExportedTypeName (type, registerAttribute); + return true; + } + public string GetExportedTypeName (TType type, RegisterAttribute? register_attribute) { string? name = null; diff --git a/tests/common/test-variations.csproj b/tests/common/test-variations.csproj index d5d820a99fad..7b64b7fc5970 100644 --- a/tests/common/test-variations.csproj +++ b/tests/common/test-variations.csproj @@ -25,6 +25,8 @@ + + @@ -168,6 +170,17 @@ + + compatibility + <_TestVariationApplied>true + + + + strict + managed-static + <_TestVariationApplied>true + + <_InvalidTestVariations Include="$(TestVariation.Split('|'))" Exclude="@(TestVariations)" /> diff --git a/tests/xharness/Jenkins/TestVariationsFactory.cs b/tests/xharness/Jenkins/TestVariationsFactory.cs index 72984f51eeb9..48c6a69f707e 100644 --- a/tests/xharness/Jenkins/TestVariationsFactory.cs +++ b/tests/xharness/Jenkins/TestVariationsFactory.cs @@ -113,10 +113,12 @@ IEnumerable GetTestData (RunTestTask test) yield return new TestData { Variation = "Debug (interpreter)", TestVariation = "interpreter", Ignored = ignore }; yield return new TestData { Variation = "Release (interpreter)", TestVariation = "release|interpreter", Ignored = ignore }; } + yield return new TestData { Variation = $"Release (compat inline Class.GetHandle)", TestVariation = "inline-class-gethandle-compat|release", Ignored = ignore }; + yield return new TestData { Variation = $"Release (strict inline Class.GetHandle)", TestVariation = "inline-class-gethandle-strict|release", Ignored = ignore }; yield return new TestData { Variation = $"Release (compat inline dlfcn)", TestVariation = "inline-dlfcn-methods-compat|release", Ignored = ignore }; yield return new TestData { Variation = $"Release (strict inline dlfcn, link sdk)", TestVariation = "inline-dlfcn-methods-strict|linksdk|release", Ignored = ignore }; if (mac_supports_arm64) - yield return new TestData { Variation = $"Release (NativeAOT, .NET 11 defaults)", TestVariation = "inline-dlfcn-methods-strict|nativeaot|release", Ignored = ignore, RuntimeIdentifier = arm64_sim_runtime_identifier }; // it's necessary to specify RID, because NativeAOT defaults to building for device + yield return new TestData { Variation = $"Release (NativeAOT, .NET 11 defaults)", TestVariation = "inline-dlfcn-methods-strict|inline-class-gethandle-strict|nativeaot|release", Ignored = ignore, RuntimeIdentifier = arm64_sim_runtime_identifier }; // it's necessary to specify RID, because NativeAOT defaults to building for device break; case "introspection": if (mac_supports_arm64) diff --git a/tools/common/DerivedLinkContext.cs b/tools/common/DerivedLinkContext.cs index da5f8090e812..2a53a9457730 100644 --- a/tools/common/DerivedLinkContext.cs +++ b/tools/common/DerivedLinkContext.cs @@ -41,6 +41,9 @@ public class DerivedLinkContext : LinkContext { // true/false = corresponding constant value Dictionary? isdirectbinding_value; + // A map from Objective-C class name to C# type + Dictionary? objectiveCTypeInfo; + // Store interfaces the linker has linked away so that the static registrar can access them. public Dictionary> ProtocolImplementations { get; private set; } = new Dictionary> (); // Store types the linker has linked away so that the static registrar can access them. @@ -73,6 +76,7 @@ public AssemblyDefinition Corlib { return corlib; } } + public HashSet? CachedIsNSObject { get { return cached_isnsobject; } set { cached_isnsobject = value; } @@ -83,6 +87,11 @@ public HashSet? CachedIsNSObject { set { isdirectbinding_value = value; } } + public Dictionary? ObjectiveCTypeInfo { + get { return objectiveCTypeInfo; } + set { objectiveCTypeInfo = value; } + } + public IList DataContract { get { return srs_data_contract; @@ -236,13 +245,17 @@ public void AddLinkedAwayType (TypeDefinition td) return null; } - public bool HasAvailabilityAttributesShowingUnavailableInSimulator (ICustomAttributeProvider type, MethodDefinition? methodForErrorReporting = null) +#if !LEGACY_TOOLS + public bool HasAvailabilityAttributesShowingUnavailableInSimulator (ICustomAttributeProvider? type, MethodDefinition? methodForErrorReporting = null) { + if (type is null) + return false; + if (!App.IsSimulatorBuild) throw ErrorHelper.CreateError (99, "HasAvailabilityAttributesShowingUnavailableInSimulator should not be called when not building for the simulator. Please file an issue at https://github.com/dotnet/macios/issues."); if (!type.HasCustomAttributes) - return true; // no attributes so say otherwise, so available + return false; // no attributes so say otherwise, so available foreach (var attrib in type.CustomAttributes) { if (attrib.AttributeType.Is ("ObjCRuntime", "UnsupportedSimulatorAttribute")) { @@ -305,6 +318,7 @@ public bool HasAvailabilityAttributesShowingUnavailableInSimulator (ICustomAttri return false; } +#endif #if !LEGACY_TOOLS class AttributeStorage : ICustomAttribute { diff --git a/tools/common/FileUtils.cs b/tools/common/FileUtils.cs index f8b3c617674f..7b406fd24ed6 100644 --- a/tools/common/FileUtils.cs +++ b/tools/common/FileUtils.cs @@ -75,5 +75,19 @@ public static bool UpdateFile (string targetFile, Action createOutput) } } + + public static void WriteIfDifferent (string path, string contents, Action log) + { + if (!File.Exists (path)) { + log ($"File '{path}' contents are not up-to-date, because the file doesn't exist."); + File.WriteAllText (path, contents); + } else if (string.Equals (contents, File.ReadAllText (path), StringComparison.Ordinal)) { + log ($"File '{path}' contents are up-to-date."); + return; + } else { + log ($"File '{path}' contents are not up-to-date."); + File.WriteAllText (path, contents); + } + } } } diff --git a/tools/common/PathUtils.cs b/tools/common/PathUtils.cs index 19dcc31df8fe..d3d2013fe39e 100644 --- a/tools/common/PathUtils.cs +++ b/tools/common/PathUtils.cs @@ -422,5 +422,25 @@ static bool IsLongPathsEnabledRegistry { } } } + + public static void CreateDirectoryForFile (string? file) + { +#if NET + if (string.IsNullOrEmpty (file)) +#else + if (string.IsNullOrEmpty (file) || file is null) +#endif + return; + + var dir = Path.GetDirectoryName (file); +#if NET + if (string.IsNullOrEmpty (dir)) +#else + if (string.IsNullOrEmpty (dir) || dir is null) +#endif + return; + + Directory.CreateDirectory (dir); + } } } diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 01e9a8189bca..e95e814e6a82 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -1679,7 +1679,7 @@ bool CollectAvailabilityAttributes (IEnumerable attributes, ou return false; } - protected override Version? GetSdkIntroducedVersion (TypeReference obj, out string? message) + public override Version? GetSdkIntroducedVersion (TypeReference obj, out string? message) { TypeDefinition td = obj.Resolve (); diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs index 5013997108d1..1b3908f009ea 100644 --- a/tools/dotnet-linker/AppBundleRewriter.cs +++ b/tools/dotnet-linker/AppBundleRewriter.cs @@ -70,6 +70,7 @@ public AssemblyDefinition SystemConsoleAssembly { Dictionary> type_map = new (); Dictionary method_map = new (); Dictionary field_map = new (); + Dictionary created_types = new (); public AppBundleRewriter (LinkerConfiguration configuration) { @@ -1450,6 +1451,7 @@ public void ClearCurrentAssembly () type_map.Clear (); method_map.Clear (); field_map.Clear (); + created_types.Clear (); } public CustomAttribute CreateAttribute (MethodReference constructor) @@ -1676,5 +1678,53 @@ static bool DebugAttributes { return debug_attributes.Value; } } + + public TypeDefinition GetOrCreateType (ModuleDefinition module, string @namespace, string @typename, out bool created) + { + created = false; + + var fullName = @namespace + "." + typename; + if (!created_types.TryGetValue (fullName, out var cachedTypeDefinition)) { + cachedTypeDefinition = module.Types.FirstOrDefault (t => t.Namespace == @namespace && t.Name == typename); + if (cachedTypeDefinition is null) { + cachedTypeDefinition = new TypeDefinition (@namespace, typename, TypeAttributes.Public | TypeAttributes.Sealed, module.TypeSystem.Object); + module.Types.Add (cachedTypeDefinition); + created = true; + } + created_types [fullName] = cachedTypeDefinition; + } + + return cachedTypeDefinition; + } + + public MethodDefinition CreateInternalPInvoke (ModuleDefinition module, string @namespace, string @typename, string methodName, out bool created) + { + var cachedTypeDefinition = GetOrCreateType (module, @namespace, @typename, out _); + var nativeMethod = methodName; + var rv = cachedTypeDefinition.Methods.FirstOrDefault (m => m.Name == methodName); + if (rv is not null) { + created = false; + return rv; // already exists, no need to create it again + } + + // [DllImport ("__Internal")] + // static extern IntPtr {methodName} (); + + rv = new MethodDefinition (methodName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.PInvokeImpl, System_IntPtr); + rv.IsPreserveSig = true; + + var mod = module.ModuleReferences.FirstOrDefault (mr => mr.Name == "__Internal"); + if (mod is null) { + mod = new ModuleReference ("__Internal"); + module.ModuleReferences.Add (mod); + } + rv.PInvokeInfo = new PInvokeInfo (PInvokeAttributes.CharSetNotSpec | PInvokeAttributes.CallConvCdecl, nativeMethod, mod); + + cachedTypeDefinition.Methods.Add (rv); + + created = true; + + return rv; + } } } diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index 5de07dbced9d..799f33a7b5c2 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -32,6 +32,7 @@ public class LinkerConfiguration { public bool HybridGlobalization { get; private set; } public InlineDlfcnMethodsMode InlineDlfcnMethods { get; set; } public bool InlineDlfcnMethodsEnabled => InlineDlfcnMethods != InlineDlfcnMethodsMode.Disabled; + public InlineClassGetHandleMode InlineClassGetHandle { get; set; } // Per-assembly field symbols collected by InlineDlfcnMethodsStep, keyed by assembly name. public Dictionary> InlinedDlfcnFields { get; } = new Dictionary> (); // All [Field] symbol names collected by ProcessExportedFields, used in compatibility mode. @@ -45,6 +46,7 @@ public class LinkerConfiguration { public string RelativeAppBundlePath { get; private set; } = string.Empty; public Version? SdkVersion { get; private set; } public string SdkRootDirectory { get; private set; } = string.Empty; + public string TypeMapFilePath { get; set; } = string.Empty; public int Verbosity => Driver.Verbosity; public string XamarinNativeLibraryDirectory { get; private set; } = string.Empty; @@ -210,11 +212,17 @@ public static LinkerConfiguration GetInstance (LinkContext context) case "FrameworkAssembly": FrameworkAssemblies.Add (value); break; + case "InlineClassGetHandle": + if (Enum.TryParse (value, true, out var inlineClassGetHandleMode)) + InlineClassGetHandle = inlineClassGetHandleMode; + else if (string.IsNullOrEmpty (value)) + InlineClassGetHandle = InlineClassGetHandleMode.Disabled; + else + throw new InvalidOperationException ($"Unknown InlineClassGetHandle value: {value}"); + break; case "InlineDlfcnMethods": if (Enum.TryParse (value, true, out var inlineDlfcnMode)) InlineDlfcnMethods = inlineDlfcnMode; - else if (string.Equals (value, "compatibility", StringComparison.OrdinalIgnoreCase)) - InlineDlfcnMethods = InlineDlfcnMethodsMode.Compat; else if (string.IsNullOrEmpty (value)) InlineDlfcnMethods = InlineDlfcnMethodsMode.Disabled; else @@ -377,6 +385,9 @@ public static LinkerConfiguration GetInstance (LinkContext context) case "TypeMapAssemblyName": Application.TypeMapAssemblyName = value; break; + case "TypeMapFilePath": + TypeMapFilePath = value; + break; case "TypeMapOutputDirectory": Application.TypeMapOutputDirectory = value; break; @@ -567,6 +578,7 @@ public void Write () Console.WriteLine ($" SdkRootDirectory: {SdkRootDirectory}"); Console.WriteLine ($" SdkVersion: {SdkVersion}"); Console.WriteLine ($" TypeMapAssemblyName: {Application.TypeMapAssemblyName}"); + Console.WriteLine ($" TypeMapFilePath: {TypeMapFilePath}"); Console.WriteLine ($" TypeMapOutputDirectory: {Application.TypeMapOutputDirectory}"); Console.WriteLine ($" UseInterpreter: {Application.UseInterpreter}"); Console.WriteLine ($" UseLlvm: {Application.IsLLVM}"); @@ -665,4 +677,12 @@ public enum InlineDlfcnMethodsMode { Disabled, Strict, Compat, + Compatibility = Compat, +} + +public enum InlineClassGetHandleMode { + Disabled, + Strict, + Compat, + Compatibility = Compat, } diff --git a/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs b/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs new file mode 100644 index 000000000000..5dd061544f91 --- /dev/null +++ b/tools/dotnet-linker/Steps/InlineClassGetHandleStep.cs @@ -0,0 +1,184 @@ +using System.Linq; +using System.Text; + +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Linker; +using Mono.Tuner; + +using Xamarin.Bundler; + +#nullable enable + +namespace Xamarin.Linker.Steps; + +// See docs/code/class-handles.md for an overview of class handle handling. + +// Find all the references to Objective-C classes for each assembly. +// * If an assembly is trimmed, we replace calls to (inline) Class.GetHandle with a P/Invoke that fetches the class in question. +// * If an assembly is not trimmed, we do not inline Class.GetHandle, but we keep a list of all the Objective-C classes that are used, so that tell the native linker about them so they're not linked away by the native linker. +// +// The sticky problem is that we can't inspect the managed output from NativeAOT, which means +// we can't list the Objective-C classes NativeAOT-compiled assemblies using. To get around this +// problem, we convert calls to Class.GetHandle to a P/Invoke which fetches the native Objective-C +// class handle directly - because we _can_ list the P/Invoke calls from NativeAOT-compiled code. +// +// For non-trimmed assemblies, we don't need to do this, because we know nothing from the assembly +// will be trimmed away. This has the added advantage of being Hot Reload compatible, because we +// can't modify assemblies when we're doing Hot Reload. + +public class InlineClassGetHandleStep : AssemblyModifierStep { + + protected override string Name { get; } = "Inline Class GetHandle"; + protected override int ErrorCode { get; } = 2260; + + bool strictMode; + bool? inlining_enabled; + + Dictionary objectiveCTypeMap = new (); + + protected override void TryProcess () + { + strictMode = Configuration.InlineClassGetHandle == InlineClassGetHandleMode.Strict; + + if (strictMode && Configuration.Application.Registrar == Bundler.RegistrarMode.Dynamic) { + Report (ErrorHelper.CreateError (Configuration.Application, 2258, null, Errors.MX2258)); + } + + objectiveCTypeMap = DerivedLinkContext.StaticRegistrar.Types.ToDictionary (v => v.Value.ExportedName, v => v.Value); + + if (!string.IsNullOrEmpty (Configuration.TypeMapFilePath)) { + var sb = new StringBuilder (); + foreach (var info in objectiveCTypeMap.Values.OrderBy (v => v.ExportedName)) { + var td = info.Type.Resolve (); + if (td is null) + continue; + var introduced = DerivedLinkContext.StaticRegistrar.GetSdkIntroducedVersion (td, out _); + if (Frameworks.TryGetFramework (App, td, out string? framework)) + sb.AppendLine ($"Class={info.ExportedName}|Framework={framework}|Introduced={introduced}"); + } + Driver.WriteIfDifferent (Configuration.TypeMapFilePath, sb.ToString ()); + } + + base.TryProcess (); + } + + protected override bool IsActiveFor (AssemblyDefinition assembly) + { + if (!Configuration.Profile.IsOrReferencesProductAssembly (assembly)) + return false; + + // we have to process both trimmed and non-trimmed assemblies. + + return true; + } + + protected override bool ModifyAssembly (AssemblyDefinition assembly) + { + inlining_enabled = Annotations.GetAction (assembly) == AssemblyAction.Link; + var modified = base.ModifyAssembly (assembly); + inlining_enabled = null; + return modified; + } + + protected override bool ProcessType (TypeDefinition type) + { + var modified = false; + + if (inlining_enabled == true) { + modified |= ProcessMethods (type); + } else { + if (ListExportedSymbols.TryGetRequiredObjectiveCType (DerivedLinkContext, type, out var exportedName)) { + DerivedLinkContext.RequiredSymbols.AddObjectiveCClass (exportedName).AddMember (type); + } + } + return modified; + } + + MethodDefinition GetOrCreatePInvokeMethod (MethodDefinition callingMethod, string objectiveCClassName) + { + // [DllImport ("__Internal")] + // static extern IntPtr xamarin_Class_GetClassHandle_{objectiveCClassName}_Native (); + + return abr.CreateInternalPInvoke (callingMethod.Module, "ObjCRuntime", "Class", $"xamarin_Class_GetHandle_{objectiveCClassName}_Native", out _); + } + + protected override bool ProcessMethod (MethodDefinition method) + { + var modified = false; + + if (!method.HasBody) + return modified; + + if (method.DeclaringType.Name == "Class" && method.DeclaringType.Namespace == "ObjCRuntime") + return modified; // don't process the Class methods themselves + + foreach (var instr in method.Body.Instructions) { + if (instr.Operand is not MethodReference mr) + continue; + if (mr.DeclaringType.Name != "Class" || mr.DeclaringType.Namespace != "ObjCRuntime") + continue; + if (mr.Name != "GetHandle" && mr.Name != "GetHandleIntrinsic") + continue; + if (mr.Parameters.Count != 1) + continue; + if (!mr.Parameters [0].ParameterType.Is ("System", "String")) + continue; + if (!mr.ReturnType.Is ("ObjCRuntime", "NativeHandle")) + continue; + + var ldstr = instr.Previous; + if (ldstr.OpCode != OpCodes.Ldstr) { + Report (ErrorHelper.CreateWarning (Configuration.Application, 2259, method, Errors.MX2259, FormatMethod (method), ldstr)); + continue; + } + if (ldstr.Operand is not string objectiveCClassName) { + Report (ErrorHelper.CreateWarning (Configuration.Application, 2259, method, Errors.MX2259, FormatMethod (method), ldstr.Operand)); + continue; + } + + if (!strictMode) { + if (ListExportedSymbols.TryGetRequiredObjectiveCType (DerivedLinkContext, method.DeclaringType, out var exportedName)) { + if (exportedName != objectiveCClassName) { + Report (ErrorHelper.CreateWarning (Configuration.Application, 2263, method, Errors.MX2263, FormatMethod (method), objectiveCClassName, exportedName)); + continue; + } + } else { + Report (ErrorHelper.CreateWarning (Configuration.Application, 2264, method, Errors.MX2264, FormatMethod (method), objectiveCClassName)); + continue; + } + } + + if (DerivedLinkContext.App.IsSimulatorBuild) { + // Check if the Objective-C type is available in the simulator, and if not, don't inline the call to Class.GetHandle (because the app would fail to link) + if (objectiveCTypeMap.TryGetValue (objectiveCClassName, out var objCType)) { + if (DerivedLinkContext.HasAvailabilityAttributesShowingUnavailableInSimulator (objCType.Type.Resolve (), method)) { + Driver.Log (3, "Not inlining the call to Class.GetHandle (\"{0}\") in method {1} because the type is marked with an attribute indicating it's not available in the simulator.", objectiveCClassName, FormatMethod (method)); + continue; + } + } else { + Report (ErrorHelper.CreateWarning (Configuration.Application, 2265, method, Errors.MX2265, FormatMethod (method), objectiveCClassName)); + } + } + + ldstr.OpCode = OpCodes.Call; + ldstr.Operand = GetOrCreatePInvokeMethod (method, objectiveCClassName); + + instr.OpCode = OpCodes.Call; + instr.Operand = abr.NativeObject_op_Implicit_NativeHandle; + + modified = true; + } + + return modified; + } + + static string FormatMethod (MethodReference method) + { + var rv = method.FullName; + var idx = rv.IndexOf (' '); + if (idx > 0) + rv = rv.Substring (idx + 1); + return rv; + } +} diff --git a/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs b/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs index 80ab202f1c9c..4b8689bf259c 100644 --- a/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs +++ b/tools/dotnet-linker/Steps/InlineDlfcnMethodsStep.cs @@ -99,18 +99,15 @@ TypeDefinition GetDlfcnType (ModuleDefinition module, string @namespace, string? { var frameworkOverride = !string.IsNullOrEmpty (fieldLibraryName) ? fieldLibraryName : current_framework; var ns = string.IsNullOrEmpty (frameworkOverride) ? @namespace : frameworkOverride; - var dlfcn = module.Types.FirstOrDefault (t => t.Namespace == ns && t.Name == "Dlfcn"); - if (dlfcn is null) { - dlfcn = new TypeDefinition (ns, "Dlfcn", TypeAttributes.NotPublic | TypeAttributes.Sealed, module.TypeSystem.Object); - module.Types.Add (dlfcn); - + var rv = abr.GetOrCreateType (module, ns, "Dlfcn", out var created); + if (created) { if (!string.IsNullOrEmpty (frameworkOverride)) { - var attrib = new CustomAttribute (abr.ObjectiveCFrameworkAttribute_ctor_String); + var attrib = abr.CreateAttribute (abr.ObjectiveCFrameworkAttribute_ctor_String); attrib.ConstructorArguments.Add (new CustomAttributeArgument (abr.System_String, frameworkOverride)); - dlfcn.CustomAttributes.Add (attrib); + rv.CustomAttributes.Add (attrib); } } - return dlfcn; + return rv; } void AddField (string assemblyName, string symbolName) @@ -124,30 +121,13 @@ void AddField (string assemblyName, string symbolName) MethodDefinition GetOrCreatePInvokeMethod (MethodDefinition callingMethod, string symbolName) { - var dlfcn = GetDlfcnType (callingMethod); - var methodName = $"xamarin_Dlfcn_{symbolName}_Native"; - var nativeMethod = methodName; - var rv = dlfcn.Methods.FirstOrDefault (m => m.Name == methodName); - if (rv is not null) - return rv; // already exists, no need to create it again - // [DllImport ("__Internal")] // static extern IntPtr xamarin_Dlfcn_{symbolName}_Native (); - rv = new MethodDefinition (methodName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.PInvokeImpl, abr.System_IntPtr); - rv.IsPreserveSig = true; - - var mod = callingMethod.Module.ModuleReferences.FirstOrDefault (mr => mr.Name == "__Internal"); - if (mod is null) { - mod = new ModuleReference ("__Internal"); - callingMethod.Module.ModuleReferences.Add (mod); - } - rv.PInvokeInfo = new PInvokeInfo (PInvokeAttributes.CharSetNotSpec | PInvokeAttributes.CallConvCdecl, nativeMethod, mod); - - dlfcn.Methods.Add (rv); - - AddField (callingMethod.Module.Assembly.Name.Name, symbolName); - + var methodName = $"xamarin_Dlfcn_{symbolName}_Native"; + var rv = abr.CreateInternalPInvoke (callingMethod.Module, callingMethod.DeclaringType.Namespace, "Dlfcn", methodName, out var created); + if (created) + AddField (callingMethod.Module.Assembly.Name.Name, symbolName); return rv; } diff --git a/tools/linker/MonoTouch.Tuner/ListExportedSymbols.cs b/tools/linker/MonoTouch.Tuner/ListExportedSymbols.cs index eda79a994f73..ed13bb630d62 100644 --- a/tools/linker/MonoTouch.Tuner/ListExportedSymbols.cs +++ b/tools/linker/MonoTouch.Tuner/ListExportedSymbols.cs @@ -18,7 +18,6 @@ namespace Xamarin.Linker.Steps { public class ListExportedSymbols : BaseStep { PInvokeWrapperGenerator? state; - bool is_product_assembly; PInvokeWrapperGenerator? State { get { @@ -80,8 +79,6 @@ protected override void ProcessAssembly (AssemblyDefinition assembly) if (!hasSymbols) return; - is_product_assembly = Configuration.Profile.IsProductAssembly (assembly); - var modified = false; foreach (var type in assembly.MainModule.Types) modified |= ProcessType (type); @@ -114,40 +111,41 @@ bool ProcessType (TypeDefinition type) void AddRequiredObjectiveCType (TypeDefinition type) { + if (TryGetRequiredObjectiveCType (DerivedLinkContext, type, out var exportedName)) + DerivedLinkContext.RequiredSymbols.AddObjectiveCClass (exportedName).AddMember (type); + } + + // Returns true if the specified type represents an Objective-C class that should referenced as a required symbol, so that the native linker doesn't link it away. + public static bool TryGetRequiredObjectiveCType (DerivedLinkContext derivedLinkContext, TypeDefinition type, [NotNullWhen (true)] out string? exportedName) + { + exportedName = null; + // The product assembly only has one type we may need to keep: XamarinSwiftFunctions - if (is_product_assembly) { + if (derivedLinkContext.LinkerConfiguration.Profile.IsProductAssembly (type.Module.Assembly)) { switch (type.Name) { case "XamarinSwiftFunctions": break; default: - return; + return false; } } - var staticRegistrar = DerivedLinkContext.StaticRegistrar; + var staticRegistrar = derivedLinkContext.StaticRegistrar; if (staticRegistrar is null) - return; + return false; - var registerAttribute = staticRegistrar.GetRegisterAttribute (type); - if (registerAttribute is null) - return; - - if (!registerAttribute.IsWrapper) - return; - - if (staticRegistrar.HasProtocolAttribute (type)) - return; + if (!staticRegistrar.TryGetExportedTypeName (type, out exportedName)) + return false; - if (DerivedLinkContext.App.RequireLinkWithAttributeForObjectiveCClassSearch) { + if (derivedLinkContext.App.RequireLinkWithAttributeForObjectiveCClassSearch) { var has_linkwith_attributes = false; - if (DerivedLinkContext.App.Assemblies.TryGetValue (type.Module.Assembly, out var asm)) + if (derivedLinkContext.App.Assemblies.TryGetValue (type.Module.Assembly, out var asm)) has_linkwith_attributes = asm.HasLinkWithAttributes; if (!has_linkwith_attributes) - return; + return false; } - var exportedName = staticRegistrar.GetExportedTypeName (type, registerAttribute); - DerivedLinkContext.RequiredSymbols.AddObjectiveCClass (exportedName).AddMember (type); + return true; } bool ProcessMethod (MethodDefinition method) diff --git a/tools/mtouch/Errors.designer.cs b/tools/mtouch/Errors.designer.cs index 85e5fd3a6293..2c8ca2ecda01 100644 --- a/tools/mtouch/Errors.designer.cs +++ b/tools/mtouch/Errors.designer.cs @@ -3560,6 +3560,51 @@ public static string MX2257 { } } + /// + /// Looks up a localized string similar to The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar.. + /// + public static string MX2258 { + get { + return ResourceManager.GetString("MX2258", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unknown or unsupported pattern in call to Class.GetHandle in '{0}': {1}. The call will not be inlined. Please file an issue at https://github.com/dotnet/macios/issues/new. + /// + public static string MX2259 { + get { + return ResourceManager.GetString("MX2259", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The call to Class.GetHandle in '{0}' is trying to get the handle for the Objective-C class '{1}', but the declaring type's exported name is '{2}', not '{1}'. Since we're in compat mode, we're assuming the class should not be preserved.. + /// + public static string MX2263 { + get { + return ResourceManager.GetString("MX2263", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The call to Class.GetHandle in '{0}' is trying to get the handle for the Objective-C class '{1}', but we couldn't determine whether this class should be statically preserved or not. Since we're in compat mode, we're assuming the class should not be preserved.. + /// + public static string MX2264 { + get { + return ResourceManager.GetString("MX2264", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find a managed type for the Objective-C type '{1}' in the call to Class.GetHandle in '{0}', assuming the Objective-C type is available in the simulator.. + /// + public static string MX2265 { + get { + return ResourceManager.GetString("MX2265", resourceCulture); + } + } + /// /// Looks up a localized string similar to Could not {0} the assembly '{1}'. /// diff --git a/tools/mtouch/Errors.resx b/tools/mtouch/Errors.resx index f719755a4d48..9c41ba949ec6 100644 --- a/tools/mtouch/Errors.resx +++ b/tools/mtouch/Errors.resx @@ -1115,6 +1115,26 @@ Unknown IL sequence for method with call to Dlfcn.CachePointer: '{0}' in method '{1}'. The call will not be inlined. Please file an issue at https://github.com/dotnet/macios/issues/new + + The 'InlineClassGetHandle' option is set to 'Strict', but we're using the dynamic registrar. This is not a supported configuration, because 'Strict' mode requires exported Objective-C classes to be available at compile time, but the dynamic registrar will create them at runtime. Please either change the 'InlineClassGetHandle' option to 'Disabled' or 'Compat', or switch to using the static registrar. + + + + Unknown or unsupported pattern in call to Class.GetHandle in '{0}': {1}. The call will not be inlined. Please file an issue at https://github.com/dotnet/macios/issues/new + + + + The call to Class.GetHandle in '{0}' is trying to get the handle for the Objective-C class '{1}', but the declaring type's exported name is '{2}', not '{1}'. Since we're in compat mode, we're assuming the class should not be preserved. + + + + The call to Class.GetHandle in '{0}' is trying to get the handle for the Objective-C class '{1}', but we couldn't determine whether this class should be statically preserved or not. Since we're in compat mode, we're assuming the class should not be preserved. + + + + Could not find a managed type for the Objective-C type '{1}' in the call to Class.GetHandle in '{0}', assuming the Objective-C type is available in the simulator. + + From a51653c636ae6a7bce274c0c08cfd654a119eb17 Mon Sep 17 00:00:00 2001 From: GitHub Actions Autoformatter Date: Thu, 7 May 2026 12:27:38 +0000 Subject: [PATCH 4/4] Auto-format source code --- tools/common/DerivedLinkContext.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/common/DerivedLinkContext.cs b/tools/common/DerivedLinkContext.cs index 2a53a9457730..2bbeff2dd3f5 100644 --- a/tools/common/DerivedLinkContext.cs +++ b/tools/common/DerivedLinkContext.cs @@ -272,9 +272,10 @@ public bool HasAvailabilityAttributesShowingUnavailableInSimulator (ICustomAttri default: LinkerConfiguration.Report (LinkerConfiguration.Context, ErrorHelper.CreateWarning (App, 99, methodForErrorReporting, "Unexpected platform '{0}'. Please file an issue at https://github.com/dotnet/macios/issues.", App.Platform)); continue; - }; + } + ; } - LinkerConfiguration.Report (LinkerConfiguration.Context,ErrorHelper.CreateWarning (App, 9999, methodForErrorReporting, "'{0}' is marked with a malformed attribute: {1}. Please file an issue at https://github.com/dotnet/macios/issues.", type.AsString (), attrib.RenderAttribute ())); + LinkerConfiguration.Report (LinkerConfiguration.Context, ErrorHelper.CreateWarning (App, 9999, methodForErrorReporting, "'{0}' is marked with a malformed attribute: {1}. Please file an issue at https://github.com/dotnet/macios/issues.", type.AsString (), attrib.RenderAttribute ())); continue; } @@ -307,11 +308,11 @@ public bool HasAvailabilityAttributesShowingUnavailableInSimulator (ICustomAttri return true; } } else { - LinkerConfiguration.Report (LinkerConfiguration.Context,ErrorHelper.CreateWarning (App, 9999, methodForErrorReporting, "'{0}' is marked with a malformed attribute (invalid version): {1}. Please file an issue at https://github.com/dotnet/macios/issues.", type.AsString (), attrib.RenderAttribute ())); + LinkerConfiguration.Report (LinkerConfiguration.Context, ErrorHelper.CreateWarning (App, 9999, methodForErrorReporting, "'{0}' is marked with a malformed attribute (invalid version): {1}. Please file an issue at https://github.com/dotnet/macios/issues.", type.AsString (), attrib.RenderAttribute ())); } continue; } - LinkerConfiguration.Report (LinkerConfiguration.Context,ErrorHelper.CreateWarning (App, 9999, methodForErrorReporting, "'{0}' is marked with a malformed attribute: {1}. Please file an issue at https://github.com/dotnet/macios/issues.", type.AsString (), attrib.RenderAttribute ())); + LinkerConfiguration.Report (LinkerConfiguration.Context, ErrorHelper.CreateWarning (App, 9999, methodForErrorReporting, "'{0}' is marked with a malformed attribute: {1}. Please file an issue at https://github.com/dotnet/macios/issues.", type.AsString (), attrib.RenderAttribute ())); continue; } }