Skip to content

Commit

Permalink
[Rgen] Add the needed code to make the objc_msgSend signatures.
Browse files Browse the repository at this point in the history
Add the code that will calculate the signature needed in the
objc_msgSend which will be the method that calls the native objc
message.
  • Loading branch information
mandel-macaque committed Jan 23, 2025
1 parent 9516bf1 commit 2706f5e
Show file tree
Hide file tree
Showing 17 changed files with 925 additions and 35 deletions.
37 changes: 37 additions & 0 deletions src/rgen/Microsoft.Macios.Generator/Attributes/MarshalDirective.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

namespace Microsoft.Macios.Generator.Attributes;

record CustomMarshalDirective (string? NativePrefix, string? NativeSuffix, string? Library);

static class ExportDataExtensions {

public static bool ShouldMarshalNativeExceptions<T> (this ExportData<T> self) where T : Enum
=> self switch {
ExportData<ObjCBindings.Property> property => property.Flags.HasFlag (ObjCBindings.Property
.CustomMarshalDirective),
ExportData<ObjCBindings.Method> method => method.Flags.HasFlag (
ObjCBindings.Property.CustomMarshalDirective),
_ => false,
};

public static CustomMarshalDirective? ToCustomMarshalDirective<T> (this ExportData<T> self) where T : Enum
{
var present = self switch {
ExportData<ObjCBindings.Property> property => property.Flags.HasFlag (ObjCBindings.Property
.CustomMarshalDirective),
ExportData<ObjCBindings.Method> method => method.Flags.HasFlag (
ObjCBindings.Property.CustomMarshalDirective),
_ => false,
};
if (present) {
return new (self.NativePrefix, self.NativeSuffix, self.Library);
}

return null;
}

}
12 changes: 11 additions & 1 deletion src/rgen/Microsoft.Macios.Generator/DataModel/Property.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@ namespace Microsoft.Macios.Generator.DataModel;

public string BackingField { get; private init; }

readonly TypeInfo returnType;

/// <summary>
/// Representation of the property type.
/// </summary>
public TypeInfo ReturnType { get; } = default;
public TypeInfo ReturnType {
get => returnType;
private init {
returnType = value;
ValueParameter = new Parameter(0, returnType, "value");
}
}

/// <summary>
/// Returns if the property type is bittable.
Expand Down Expand Up @@ -62,6 +70,8 @@ namespace Microsoft.Macios.Generator.DataModel;
/// </summary>
public ImmutableArray<Accessor> Accessors { get; } = [];

public Parameter ValueParameter { get; private init; }

public Accessor? GetAccessor (AccessorKind accessorKind)
{
// careful, do not use FirstOrDefault from LINQ because we are using structs!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ symbol is IArrayTypeSymbol arrayTypeSymbol
IsSmartEnum = symbol.IsSmartEnum ();
IsArray = symbol is IArrayTypeSymbol;
IsReferenceType = symbol.IsReferenceType;
IsStruct = symbol.TypeKind == TypeKind.Struct;
IsInterface = symbol.TypeKind == TypeKind.Interface;
IsNativeIntegerType = symbol.IsNativeIntegerType;
IsNativeEnum = symbol.HasAttribute (AttributesNames.NativeEnumAttribute);
Expand Down
82 changes: 76 additions & 6 deletions src/rgen/Microsoft.Macios.Generator/DataModel/TypeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,34 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.Macios.Generator.Attributes;
using Microsoft.Macios.Generator.Extensions;

namespace Microsoft.Macios.Generator.DataModel;

/// <summary>
/// Readonly structure that represents a change in a method return type.
/// </summary>
readonly partial struct TypeInfo : IEquatable<TypeInfo> {

public static TypeInfo Void = new ("void", SpecialType.System_Void) { Parents = ["System.ValueType", "object"], };

readonly string fullyQualifiedName = string.Empty;
/// <summary>
/// Type of the parameter.
/// </summary>
public string Name { get; }
public string FullyQualifiedName {
get => fullyQualifiedName;
init {
fullyQualifiedName = value;
var index = fullyQualifiedName.LastIndexOf ('.');
Name = index != -1
? fullyQualifiedName.Substring (index + 1)
: fullyQualifiedName;
}
}

public string Name { get; private init; } = string.Empty;

/// <summary>
/// The metadata name of the type. This is normally the same as name except
Expand Down Expand Up @@ -64,6 +80,11 @@ namespace Microsoft.Macios.Generator.DataModel;
/// Returns if the return type is a reference type.
/// </summary>
public bool IsReferenceType { get; }

/// <summary>
/// Returns if the type is a struct.
/// </summary>
public bool IsStruct { get; }

/// <summary>
/// Returns if the return type is void.
Expand Down Expand Up @@ -116,7 +137,7 @@ public ImmutableArray<string> Interfaces {

internal TypeInfo (string name, SpecialType specialType)
{
Name = name;
FullyQualifiedName = name;
SpecialType = specialType;
}

Expand All @@ -126,19 +147,21 @@ internal TypeInfo (string name,
bool isBlittable = false,
bool isSmartEnum = false,
bool isArray = false,
bool isReferenceType = false) : this (name, specialType)
bool isReferenceType = false,
bool isStruct = false) : this (name, specialType)
{
IsNullable = isNullable;
IsBlittable = isBlittable;
IsSmartEnum = isSmartEnum;
IsArray = isArray;
IsReferenceType = isReferenceType;
IsStruct = isStruct;
}

/// <inheritdoc/>
public bool Equals (TypeInfo other)
{
if (Name != other.Name)
if (FullyQualifiedName != other.FullyQualifiedName)
return false;
if (SpecialType != other.SpecialType)
return false;
Expand All @@ -154,6 +177,8 @@ public bool Equals (TypeInfo other)
return false;
if (IsReferenceType != other.IsReferenceType)
return false;
if (IsStruct != other.IsStruct)
return false;
if (IsVoid != other.IsVoid)
return false;
if (EnumUnderlyingType != other.EnumUnderlyingType)
Expand Down Expand Up @@ -184,7 +209,7 @@ public override bool Equals (object? obj)
/// <inheritdoc/>
public override int GetHashCode ()
{
return HashCode.Combine (Name, IsNullable, IsBlittable, IsSmartEnum, IsArray, IsReferenceType, IsVoid);
return HashCode.Combine (FullyQualifiedName, IsNullable, IsBlittable, IsSmartEnum, IsArray, IsReferenceType, IsVoid);
}

public static bool operator == (TypeInfo left, TypeInfo right)
Expand All @@ -197,11 +222,56 @@ public override int GetHashCode ()
return !left.Equals (right);
}

const string NativeHandle = "NativeHandle";
const string IntPtr = "IntPtr";
const string UIntPtr = "UIntPtr";

public string? ToMarshallType (ReferenceKind referenceKind)
{
var type = this switch {
// special cases based on name
{ Name: "nfloat" or "NFloat" } => "nfloat",
{ Name: "nint" or "nuint"} => MetadataName,
// special string case
{ SpecialType: SpecialType.System_String} => NativeHandle, // use a NSString when we get a string

// NSObject should use the native handle
{ IsNSObject: true } => NativeHandle,
{ IsINativeObject: true } => NativeHandle,

// structs will use their name
{ IsStruct: true, SpecialType: SpecialType.System_Double } => "Double",
{ IsStruct: true } => Name,

// enums:
// IsSmartEnum: We are using a nsstring, so it should be a native handle.
// IsNativeEnum: Depends if the enum backing field kind.
// GeneralEnum: Depends on the EnumUnderlyingType

{ IsSmartEnum: true } => NativeHandle,
{ IsNativeEnum: true, EnumUnderlyingType: SpecialType.System_Int64 } => IntPtr,
{ IsNativeEnum: true, EnumUnderlyingType: SpecialType.System_UInt64 } => UIntPtr,
{ IsEnum: true, EnumUnderlyingType: not null } => EnumUnderlyingType.GetKeyword (),

// special type that is a keyword (none would be a ref type)
{ SpecialType: SpecialType.System_Void } => SpecialType.GetKeyword (),
{ SpecialType: not SpecialType.None } => MetadataName,


// This should not happen in bindings because all of the types should either be native objects
// nsobjects, or structs
{ IsReferenceType: false } => Name,

_ => null,
};
return type;
}

/// <inheritdoc/>
public override string ToString ()
{
var sb = new StringBuilder ("{");
sb.Append ($"Name: '{Name}', ");
sb.Append ($"Name: '{FullyQualifiedName}', ");
sb.Append ($"MetadataName: '{MetadataName}', ");
sb.Append ($"SpecialType: '{SpecialType}', ");
sb.Append ($"IsNullable: {IsNullable}, ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class TypeInfoComparer : IComparer<TypeInfo> {
/// <inheritdoc/>
public int Compare (TypeInfo x, TypeInfo y)
{
var returnTypeComparison = String.Compare (x.Name, y.Name, StringComparison.Ordinal);
var returnTypeComparison = String.Compare (x.FullyQualifiedName, y.FullyQualifiedName, StringComparison.Ordinal);
if (returnTypeComparison != 0)
return returnTypeComparison;
var isNullableComparison = x.IsNullable.CompareTo (y.IsNullable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ public static (Func<string, string, CompilationUnitSyntax> Getter,
if (!property.IsField)
throw new NotSupportedException ("Cannot retrieve getter for non field property.");

var fieldType = property.ReturnType.Name;
var fieldType = property.ReturnType.FullyQualifiedName;
var underlyingEnumType = property.ReturnType.EnumUnderlyingType.GetKeyword ();

Func<string, string, CompilationUnitSyntax> WrapGenericCall (string genericType,
Expand All @@ -621,28 +621,28 @@ Func<string, string, string, CompilationUnitSyntax> WithCast (Func<string, strin
#pragma warning disable format
if (property.ReturnType.IsNSObject) {
return property.ReturnType switch {
{ Name: "Foundation.NSString" } => (Getter: GetStringConstant, Setter: SetString),
{ Name: "Foundation.NSArray" } => (
Getter: WrapGenericCall (property.ReturnType.Name, GetNSObjectField),
{ FullyQualifiedName: "Foundation.NSString" } => (Getter: GetStringConstant, Setter: SetString),
{ FullyQualifiedName: "Foundation.NSArray" } => (
Getter: WrapGenericCall (property.ReturnType.FullyQualifiedName, GetNSObjectField),
Setter: SetArray),
_ => (
Getter: WrapGenericCall (property.ReturnType.Name, GetNSObjectField),
Getter: WrapGenericCall (property.ReturnType.FullyQualifiedName, GetNSObjectField),
Setter: SetObject)
};
}

// use the return type and the special type of the property to decide what getter we are going to us
return property.ReturnType switch {
// special types
{ Name: "CoreGraphics.CGSize" } => (Getter: GetCGSize, Setter: SetCGSize),
{ Name: "CoreMedia.CMTag" } => (
{ FullyQualifiedName: "CoreGraphics.CGSize" } => (Getter: GetCGSize, Setter: SetCGSize),
{ FullyQualifiedName: "CoreMedia.CMTag" } => (
Getter: WrapGenericCall ("CoreMedia.CMTag", GetStruct),
Setter: WrapThrow ()),
{ Name: "nfloat" } => (Getter: GetNFloat, Setter: SetNFloat),
{ FullyQualifiedName: "nfloat" } => (Getter: GetNFloat, Setter: SetNFloat),

// Blittable types
{ Name: "CoreMedia.CMTime" or "AVFoundation.AVCaptureWhiteBalanceGains" }
=> (Getter: WrapGenericCall (property.ReturnType.Name, GetBlittableField),
{ FullyQualifiedName: "CoreMedia.CMTime" or "AVFoundation.AVCaptureWhiteBalanceGains" }
=> (Getter: WrapGenericCall (property.ReturnType.FullyQualifiedName, GetBlittableField),
Setter: WrapThrow ()),

// enum types, decide based on its enum backing field, smart enums have to be done in the binding
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.Macios.Generator.Attributes;
using Microsoft.Macios.Generator.DataModel;
using Microsoft.Macios.Generator.Extensions;
using TypeInfo = Microsoft.Macios.Generator.DataModel.TypeInfo;

namespace Microsoft.Macios.Generator.Emitters;

static partial class BindingSyntaxFactory{
readonly static string objc_msgSend = "objc_msgSend";
readonly static string objc_msgSendSuper = "objc_msgSendSuper";

static string? GetObjCMessageSendMethodName<T> (ExportData<T> exportData,
TypeInfo returnType, ImmutableArray<Parameter> parameters, bool isSuper = false, bool isStret = false) where T : Enum
{
var flags = exportData.Flags;
if (flags is null)
// flags are not set, should be a bug, but we will return null
return null;

// the name of the objcSend method is calculated in the following way
// {CustomMarshallPrefix}_{MarshallTypeOfReturnType}_{objcSendMsg}{stret?_stret}_{string.Join('_', MarshallTypeArgs)}{nativeException?_exception}{CustomMarsahllPostfix}
// we will sue a sb to make things easy to follow
var sb = new StringBuilder ();

// first, decide if the user created a custom marshalling by checking the flags of the export data
CustomMarshalDirective? customMarshalDirective = null;
if (flags.HasCustomMarshalDirective ()) {
customMarshalDirective = exportData.ToCustomMarshalDirective ();
}

if (customMarshalDirective?.NativePrefix is not null) {
sb.Append (customMarshalDirective.NativePrefix);
} else if (flags.HasMarshalNativeExceptions ()) {
sb.Append ("xamarin_");
}

// return types do not have a reference kind
sb.Append (returnType.ToMarshallType (ReferenceKind.None));
sb.Append ('_');
// append the msg method based if it is for super or not, do not append '_' intimidatingly, since if we do
// not have parameters, we are done
sb.Append (isSuper ? objc_msgSendSuper : objc_msgSend);
if (isStret) {
sb.Append ("_stret");
}
// loop over params and get their native handler name
if (parameters.Length > 0) {
sb.Append ('_');
sb.AppendJoin ('_', parameters.Select ( p => p.Type.ToMarshallType (p.ReferenceKind)));
}

// check if we do have a custom marshall exception set for the export

// check any possible custom postfix naming
if (customMarshalDirective?.NativeSuffix is not null) {
sb.Append (customMarshalDirective.NativeSuffix);
} else if (flags.HasMarshalNativeExceptions ()) {
sb.Append ("_exception");
}
return sb.ToString ();
}

public static (string? Getter, string? Setter) GetObjCMessageSendMethods (in Property property, bool isSuper = false, bool isStret = false)
{
if (property.IsProperty) {
// the getter and the setter depend of the accessors that have been ser for the property, we do not want
// to calculate things that we wont use. The export data used will aslo depend if the getter/setter has a
// export attr attached
var getter = property.GetAccessor (AccessorKind.Getter);
string? getterMsgSend = null;
if (getter is not null) {
var getterExportData = getter.Value.ExportPropertyData ?? property.ExportPropertyData;
if (getterExportData is not null) {
getterMsgSend = GetObjCMessageSendMethodName (getterExportData.Value, property.ReturnType, [],
isSuper, isStret);
}
}

var setter = property.GetAccessor (AccessorKind.Setter);
string? setterMsgSend = null;
if (setter is not null) {
var setterExportData = setter.Value.ExportPropertyData ?? property.ExportPropertyData;
if (setterExportData is not null) {
setterMsgSend = GetObjCMessageSendMethodName (setterExportData.Value, TypeInfo.Void,
[property.ValueParameter], isSuper, isStret);
}
}
return (Getter: getterMsgSend, Setter: setterMsgSend);
}

return default;
}

public static string? GetObjCMessageSendMethod (in Method method, bool isSuper = false, bool isStret = false)
=> GetObjCMessageSendMethodName (method.ExportMethodData, method.ReturnType, method.Parameters, isSuper, isStret);

}
Loading

0 comments on commit 2706f5e

Please sign in to comment.