xamarin-macios/tests/xtro-sharpie/SimdCheck.cs

413 строки
15 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Clang.Ast;
namespace Extrospection
{
class SimdCheck : BaseVisitor
{
bool very_strict = false;
bool strict = false;
// A dictionary of native type -> managed type mapping.
class NativeSimdInfo
{
public string Managed;
public string InvalidManaged;
}
static Dictionary<string, NativeSimdInfo> type_mapping = new Dictionary<string, NativeSimdInfo> () {
{ "matrix_double2x2", new NativeSimdInfo { Managed ="MatrixDouble2x2", InvalidManaged = "Matrix2d" }},
{ "matrix_double3x3", new NativeSimdInfo { Managed = "MatrixDouble3x3", InvalidManaged = "Matrix3d" }},
{ "matrix_double4x4", new NativeSimdInfo { Managed = "MatrixDouble4x4", InvalidManaged = "Matrix4d" }},
{ "matrix_float2x2", new NativeSimdInfo { Managed = "MatrixFloat2x2", InvalidManaged = "Matrix2", }},
{ "matrix_float3x3", new NativeSimdInfo { Managed = "MatrixFloat3x3", InvalidManaged = "Matrix3", }},
{ "matrix_float4x3", new NativeSimdInfo { Managed = "MatrixFloat4x3", }},
{ "matrix_float4x4", new NativeSimdInfo { Managed = "MatrixFloat4x4", InvalidManaged = "Matrix4", }},
{ "simd_quatd", new NativeSimdInfo { Managed = "Quaternion4d", }},
{ "simd_quatf", new NativeSimdInfo { Managed = "Quaternion4", }},
{ "vector_double2", new NativeSimdInfo { Managed = "Vector2d", }},
{ "vector_double3", new NativeSimdInfo { Managed = "Vector3d", }},
{ "vector_double4", new NativeSimdInfo { Managed = "Vector4d", }},
{ "vector_float2", new NativeSimdInfo { Managed = "Vector2", }},
{ "vector_float3", new NativeSimdInfo { Managed = "Vector3", }},
{ "vector_float4", new NativeSimdInfo { Managed = "Vector4", }},
{ "vector_int2", new NativeSimdInfo { Managed = "Vector2i", }},
{ "vector_int3", new NativeSimdInfo { Managed = "Vector3i", }},
{ "vector_int4", new NativeSimdInfo { Managed = "Vector4i", }},
{ "vector_uint2", new NativeSimdInfo { Managed = "Vector2i", }},
{ "vector_uint3", new NativeSimdInfo { Managed = "Vector3i", }},
{ "vector_uint4", new NativeSimdInfo { Managed = "Vector4i", }},
// simd_doubleX is typedefed to vector_doubleX
{ "simd_double2", new NativeSimdInfo { Managed = "Vector2d" }},
// simd_floatX is typedefed to vector_floatX
{ "simd_float2", new NativeSimdInfo { Managed = "Vector2" }},
{ "simd_float3", new NativeSimdInfo { Managed = "Vector3" }},
{ "simd_float4", new NativeSimdInfo { Managed = "Vector4" }},
{ "simd_float3x3", new NativeSimdInfo { Managed = "MatrixFloat3x3", InvalidManaged = "Matrix3" }},
{ "simd_float4x4", new NativeSimdInfo { Managed = "MatrixFloat4x4", InvalidManaged = "Matrix4" }},
// The native definition is two 'vector_float2' fields.
// The managed definition matches this (two 'Vector2' fields), and should work fine.
{ "GKQuad", new NativeSimdInfo { Managed = "GKQuad" }},
// The native definition is two 'vector_float3' fields.
// In this case each element uses 16 bytes (4 floats) due to padding.
// The managed definition is two Vector3 fields, and does *not*
// match the native definition (missing the padding).
// It still works because we're marshalling this struct manually ([MarshalDirective]).
{ "GKBox", new NativeSimdInfo { Managed = "GKBox", }},
// The native definition is 'vector_float3 points[3]' - an array of three vector_float3.
// In this case each element uses 16 bytes (4 floats) due to padding.
// The managed definition is just an array of Vector3, but luckily
// it's a private field, so we might be able to improve this later. Right now we're marshalling
// this struct manually ([MarshalDirective]), so managed code should get correct
// results.
{ "GKTriangle", new NativeSimdInfo { Managed = "GKTriangle", }},
// This is a 'vector_int4, represented by a Vector4i in managed code,
// which means it's matching the native definition.
{ "MDLVoxelIndex", new NativeSimdInfo { Managed = "MDLVoxelIndex" }},
// In managed code this is struct of two Vector4, so it's matching the native definition.
{ "MDLVoxelIndexExtent", new NativeSimdInfo { Managed = "MDLVoxelIndexExtent" }},
// In managed code this is a struct of two Vector3, so it's *not* matching
// the native definition. However, since we're manually marshalling this type
// (using [MarshalDirective]), managed code doesn't get incorrect results.
{ "MDLAxisAlignedBoundingBox", new NativeSimdInfo { Managed = "MDLAxisAlignedBoundingBox", }},
{ "MPSAxisAlignedBoundingBox", new NativeSimdInfo { Managed = "MPSAxisAlignedBoundingBox" }},
// The managed definition is identical to the native definition
{ "MPSImageHistogramInfo", new NativeSimdInfo { Managed = "MPSImageHistogramInfo" }},
};
static Dictionary<string, bool> managed_simd_types; // bool: invalid_for_simd
static SimdCheck ()
{
managed_simd_types = new Dictionary<string, bool> ();
foreach (var kvp in type_mapping) {
managed_simd_types [kvp.Value.Managed] = false;
if (!string.IsNullOrEmpty (kvp.Value.InvalidManaged))
managed_simd_types [kvp.Value.InvalidManaged] = true;
}
}
class ManagedSimdInfo
{
public MethodDefinition Method;
public bool ContainsInvalidMappingForSimd;
}
Dictionary<string, ManagedSimdInfo> managed_methods = new Dictionary<string, ManagedSimdInfo> ();
public override void VisitManagedMethod (MethodDefinition method)
{
var type = method.DeclaringType;
if (!type.IsNested && type.IsNotPublic)
return;
if (type.IsNested && (type.IsNestedPrivate || type.IsNestedAssembly || type.IsNestedFamilyAndAssembly))
return;
if (method.IsPrivate || method.IsAssembly || method.IsFamilyAndAssembly)
return; // Don't care about non-visible types
if (type.Namespace == "Simd" || type.Namespace.StartsWith ("OpenTK", StringComparison.Ordinal))
return; // We're assuming everything in the Simd and OpenTK namespaces can be ignored (the former because it's correctly written, the latter because it doesn't map to native simd types).
if (method.HasCustomAttributes && method.CustomAttributes.Where ((v) => v.Constructor.DeclaringType.Name == "ExtensionAttribute").Any ())
return; // Extension methods can't be mapped.
var invalid_simd_type = false;
var contains_simd_types = ContainsSimdTypes (method, ref invalid_simd_type);
var key = method.GetName ();
if (key == null) {
if (method.IsObsolete ())
return; // Don't care about obsolete API.
if (contains_simd_types && very_strict) {
// We can't map this method to a native function.
var framework = method.DeclaringType.Namespace;
Log.On (framework).Add ($"!missing-simd-native-signature! {method}");
}
return;
}
ManagedSimdInfo existing;
if (managed_methods.TryGetValue (key, out existing)) {
if (very_strict) {
var sorted = Helpers.Sort (existing.Method, method);
var framework = sorted.Item1.DeclaringType.Namespace;
Log.On (framework).Add ($"!duplicate-type-mapping! same key '{key}' for both '{sorted.Item1.FullName}' and '{sorted.Item2.FullName}'");
}
} else {
managed_methods [key] = new ManagedSimdInfo {
Method = method, ContainsInvalidMappingForSimd = invalid_simd_type
};
}
}
bool ContainsSimdTypes (MethodDefinition method, ref bool invalid_for_simd)
{
if (IsSimdType (method.ReturnType, ref invalid_for_simd))
return true;
if (method.HasParameters) {
foreach (var param in method.Parameters)
if (IsSimdType (param.ParameterType, ref invalid_for_simd))
return true;
}
return false;
}
bool IsSimdType (TypeReference td, ref bool invalid_for_simd)
{
return managed_simd_types.TryGetValue (td.Name, out invalid_for_simd);
}
bool ContainsSimdTypes (ObjCMethodDecl decl, ref string simd_type, ref bool requires_marshal_directive)
{
if (IsSimdType (decl, decl.ReturnQualType, ref simd_type, ref requires_marshal_directive))
return true;
var is_simd_type = false;
foreach (var param in decl.Parameters)
is_simd_type |= IsSimdType (decl, param.QualType, ref simd_type, ref requires_marshal_directive);
return is_simd_type;
}
bool IsExtVector (Decl decl, QualType type, ref string simd_type)
{
var rv = false;
var t = type.CanonicalQualType.Type;
// Unpoint the type
var pointerType = t as Clang.Ast.PointerType;
if (pointerType != null)
t = pointerType.PointeeQualType.Type;
if (t.Kind == TypeKind.ExtVector) {
rv = true;
} else {
var r = (t as RecordType)?.Decl;
if (r != null) {
foreach (var f in r.Fields) {
var qt = f.QualType.CanonicalQualType.Type;
if (qt.Kind == TypeKind.ExtVector) {
rv = true;
break;
}
var at = qt as ConstantArrayType;
if (at != null) {
if (at.ElementType.Type.Kind == TypeKind.ExtVector) {
rv = true;
break;
}
}
}
}
}
var typeName = type.ToString ();
if (!rv && typeName.Contains ("simd")) {
var framework = Helpers.GetFramework (decl);
Log.On (framework).Add ($"!unknown-simd-type! Could not detect that {typeName} is a Simd type, but its name contains 'simd'. Something needs fixing in SimdCheck.cs");
}
if (rv)
simd_type = typeName;
return rv;
}
bool IsSimdType (Decl decl, QualType type, ref string simd_type, ref bool requires_marshal_directive)
{
var str = Undecorate (type.ToString ());
if (type_mapping.TryGetValue (str, out var info)) {
requires_marshal_directive = true;
simd_type = str;
return true;
}
if (IsExtVector (decl, type, ref simd_type)) {
var framework = Helpers.GetFramework (decl);
Log.On (framework).Add ($"!unknown-simd-type-mapping! The Simd type {simd_type} does not have a mapping to a managed type. Please add one in SimdCheck.cs");
}
return false;
}
string Undecorate (string native_name)
{
const string _const = "const ";
if (native_name.StartsWith (_const, StringComparison.Ordinal))
return Undecorate (native_name.Substring (_const.Length));
const string _struct = "struct ";
if (native_name.StartsWith (_struct, StringComparison.Ordinal))
return Undecorate (native_name.Substring (_struct.Length));
const string _nsrefinedforswift = "NS_REFINED_FOR_SWIFT ";
if (native_name.StartsWith (_nsrefinedforswift, StringComparison.Ordinal))
return Undecorate (native_name.Substring (_nsrefinedforswift.Length));
const string _nsreturnsinnerpointer = "NS_RETURNS_INNER_POINTER ";
if (native_name.StartsWith (_nsreturnsinnerpointer, StringComparison.Ordinal))
return Undecorate (native_name.Substring (_nsreturnsinnerpointer.Length));
const string _Nonnull = " _Nonnull";
if (native_name.EndsWith (_Nonnull, StringComparison.Ordinal))
return Undecorate (native_name.Substring (0, native_name.Length - _Nonnull.Length));
const string _Nullable = " _Nullable";
if (native_name.EndsWith (_Nullable, StringComparison.Ordinal))
return Undecorate (native_name.Substring (0, native_name.Length - _Nullable.Length));
const string _star = " *";
if (native_name.EndsWith (_star, StringComparison.Ordinal))
return Undecorate (native_name.Substring (0, native_name.Length - _star.Length));
return native_name;
}
bool HasMarshalDirective (ICustomAttributeProvider provider)
{
if (provider?.HasCustomAttributes != true)
return false;
foreach (var ca in provider.CustomAttributes)
if (ca.Constructor.DeclaringType.Name == "MarshalDirective")
return true;
return false;
}
void CheckMarshalDirective (MethodDefinition method, string simd_type)
{
if (!method.HasBody)
return;
if (method.IsObsolete ())
return;
// The [MarshalDirective] attribute isn't copied to the generated code,
// so instead apply some heuristics and detect calls to the xamarin_simd__ P/Invoke,
// and if there are any, then assume the method binding has a [MarshalDirective].
var body = method.Body;
var anyCalls = false;
foreach (var i in body.Instructions) {
switch (i.OpCode.Code) {
case Mono.Cecil.Cil.Code.Call:
case Mono.Cecil.Cil.Code.Calli:
case Mono.Cecil.Cil.Code.Callvirt:
var mr = i.Operand as MethodReference;
if (mr != null) {
if (mr.Name.StartsWith ("xamarin_simd__", StringComparison.Ordinal))
return;
if (mr.Name.StartsWith ("xamarin_vector_float3__", StringComparison.Ordinal))
return;
}
anyCalls = true;
break;
default:
break;
}
}
// If the method doesn't call anywhere, it can't be broken.
// For instance if the method just throws an exception.
if (!anyCalls)
return;
var framework = method.DeclaringType.Namespace;
Log.On (framework).Add ($"!wrong-simd-missing-marshaldirective! {method}: simd type: {simd_type}");
}
public override void VisitObjCMethodDecl (ObjCMethodDecl decl, VisitKind visitKind)
{
if (visitKind != VisitKind.Enter)
return;
// don't process methods (or types) that are unavailable for the current platform
if (!decl.IsAvailable () || !(decl.DeclContext as Decl).IsAvailable ())
return;
var framework = Helpers.GetFramework (decl);
if (framework == null)
return;
var simd_type = string.Empty;
var requires_marshal_directive = false;
var native_simd = ContainsSimdTypes (decl, ref simd_type, ref requires_marshal_directive);
ManagedSimdInfo info;
managed_methods.TryGetValue (decl.GetName (), out info);
var method = info?.Method;
if (!native_simd) {
if (method != null) {
// The managed method uses types that were incorrectly used in place of the correct Simd types,
// but the native method doesn't use the native Simd types. This means the binding is correct.
} else {
// Neither the managed nor the native method have anything to do with Simd types.
}
return;
}
if (method == null) {
// Could not map the native method to a managed method.
// This needs investigation, to see why the native method couldn't be mapped.
// Check if this is new API, in which case it probably couldn't be mapped because we haven't bound it.
var is_new = false;
var attrs = decl.Attrs.ToList ();
var parentClass = decl.DeclContext as Decl;
if (parentClass != null)
attrs.AddRange (parentClass.Attrs);
foreach (var attr in attrs) {
var av_attr = attr as AvailabilityAttr;
if (av_attr == null)
continue;
if (av_attr.Platform.Name != "ios")
continue;
if (av_attr.Introduced.Major >= 11) {
is_new = true;
break;
}
}
if (is_new && !very_strict)
return;
if (!strict)
return;
Log.On (framework).Add ($"!missing-simd-managed-method! {decl}: could not find a managed method for the native method {decl.GetName ()} (selector: {decl.Selector}). Found the simd type '{simd_type}' in the native signature.");
return;
}
if (!info.ContainsInvalidMappingForSimd) {
// The managed method does not have any types that are incorrect for Simd.
if (requires_marshal_directive)
CheckMarshalDirective (method, simd_type);
return;
}
if (method.IsObsolete ()) {
// We have a potentially broken managed method, but it's obsolete. That's fine.
return;
}
if (requires_marshal_directive)
CheckMarshalDirective (method, simd_type);
// We have a potentially broken managed method. This needs fixing/investigation.
Log.On (framework).Add ($"!unknown-simd-type-in-signature! {method}: the native signature has a simd type ({simd_type}), while the corresponding managed method is using an incorrect (non-simd) type.");
}
}
}