Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SwiftBindings] Added TypeMetadata, removed SwiftTypeInfo #2804

Merged
merged 3 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public void Emit(IndentedTextWriter writer, IEnvironment env, Conductor conducto
unsafe
{
// Apply struct layout attributes
// TODO: refactor to use type metadata
writer.WriteLine($"[StructLayout(LayoutKind.Sequential, Size = {swiftTypeInfo.Value.ValueWitnessTable->Size})]");
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/Swift.Bindings/src/Parser/SwiftABIParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ private List<BaseDecl> CollectDeclarations(IEnumerable<Node> nodes, BaseDecl par
{
case "Struct":
case "Enum":
// TODO: fix this code to not use static metadata objects if possible
metadataPtr = DynamicLibraryLoader.invoke(_dylibPath, GetMetadataAccessor(node));
var swiftTypeInfo = new SwiftTypeInfo { MetadataPtr = metadataPtr };

Expand All @@ -292,7 +293,7 @@ private List<BaseDecl> CollectDeclarations(IEnumerable<Node> nodes, BaseDecl par
{
decl = CreateClassDecl(node, parentDecl, moduleDecl);
}
typeRecord.SwiftTypeInfo = swiftTypeInfo;
typeRecord.SwiftTypeInfo = swiftTypeInfo;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look intentional? Unindent by one space?

break;

case "Class":
Expand Down
199 changes: 199 additions & 0 deletions src/Swift.Runtime/src/Metadata/TypeMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;

namespace Swift.Runtime;

/// <summary>
/// Flags used to describe types
/// </summary>
[Flags]
public enum TypeMetadataFlags {
/// <summary>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to add a None = 0 value, since this enum represents flags?

/// The metadata is not an actual type
/// </summary>
MetadataKindIsNonType = 0x400,
/// <summary>
/// The metadata doesn't live on the heap
/// </summary>
MetadataKindIsNonHeap = 0x200,
/// <summary>
/// The type is private to the runtime
/// </summary>
MetadataKindIsRuntimePrivate = 0x100,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is MetadataKind a common prefix for all values, or will there be more flags that aren't MetadataKind flags? If the former, the prefix should be removed:

Suggested change
MetadataKindIsNonType = 0x400,
/// <summary>
/// The metadata doesn't live on the heap
/// </summary>
MetadataKindIsNonHeap = 0x200,
/// <summary>
/// The type is private to the runtime
/// </summary>
MetadataKindIsRuntimePrivate = 0x100,
IsNonType = 0x400,
/// <summary>
/// The metadata doesn't live on the heap
/// </summary>
IsNonHeap = 0x200,
/// <summary>
/// The type is private to the runtime
/// </summary>
IsRuntimePrivate = 0x100,

}

/// <summary>
/// The type represented by the metadata
/// </summary>
public enum TypeMetadataKind {
/// <summary>
/// None - errror
/// </summary>
None = 0,
/// <summary>
/// The metadata represents a struct
/// </summary>
Struct = 0 | TypeMetadataFlags.MetadataKindIsNonHeap,
/// <summary>
/// The metadata represents an enum
/// </summary>
Enum = 1 | TypeMetadataFlags.MetadataKindIsNonHeap,
/// <summary>
/// The metadata represents an optional type
/// </summary>
Optional = 2 | TypeMetadataFlags.MetadataKindIsNonHeap,
/// <summary>
/// The metadata represents an non-swift class
/// </summary>
ForeignClass = 3 | TypeMetadataFlags.MetadataKindIsNonHeap,
/// <summary>
/// The metadata represents an opaque type
/// </summary>
Opaque = 0 | TypeMetadataFlags.MetadataKindIsRuntimePrivate | TypeMetadataFlags.MetadataKindIsNonHeap,
/// <summary>
/// The metadata represents a tuple
/// </summary>
Tuple = 1 | TypeMetadataFlags.MetadataKindIsRuntimePrivate | TypeMetadataFlags.MetadataKindIsNonHeap,
/// <summary>
/// The metadata represents a closure/function
/// </summary>
Function = 2 | TypeMetadataFlags.MetadataKindIsRuntimePrivate | TypeMetadataFlags.MetadataKindIsNonHeap,
/// <summary>
/// The metadata represents a protocol
/// </summary>
Protocol = 3 | TypeMetadataFlags.MetadataKindIsRuntimePrivate | TypeMetadataFlags.MetadataKindIsNonHeap,
/// <summary>
/// The metadata represents a type of a TypeMetadata type
/// </summary>
Metatype = 4 | TypeMetadataFlags.MetadataKindIsRuntimePrivate | TypeMetadataFlags.MetadataKindIsNonHeap,
/// <summary>
/// The metadata represents an Objective C wrapper
/// </summary>
ObjCClassWrapper = 5 | TypeMetadataFlags.MetadataKindIsRuntimePrivate | TypeMetadataFlags.MetadataKindIsNonHeap,
/// <summary>
/// The metadata represents a type of an existential container
/// </summary>
ExistentialMetatype = 6 | TypeMetadataFlags.MetadataKindIsRuntimePrivate | TypeMetadataFlags.MetadataKindIsNonHeap,
/// <summary>
/// The metadata represents a heap local variable
/// </summary>
HeapLocalVariable = 0 | TypeMetadataFlags.MetadataKindIsNonType,
/// <summary>
/// The metadata represents a generic heap local variable
/// </summary>
HeapGenericLocalVariable = 0 | TypeMetadataFlags.MetadataKindIsNonType | TypeMetadataFlags.MetadataKindIsRuntimePrivate,
/// <summary>
/// The metadata represents an error
/// </summary>
ErrorObject = 1 | TypeMetadataFlags.MetadataKindIsNonType | TypeMetadataFlags.MetadataKindIsRuntimePrivate,
// Swift source code says that for fixed values, this will never exceed 0x7ff,
// but all class types will be 0x800 and above
/// <summary>
/// The metadata represents a class
/// </summary>
Class = 0x800
}


/// <summary>
/// Represents the type metadata for a Swift type
/// </summary>
public readonly struct TypeMetadata : IEquatable<TypeMetadata> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How this correlates with SwiftMetadata? Do we plan to use metadata for type semantics on the C# side or only as an unmanaged pointer for callconv?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SwiftMetadata is based off the same type, but the difference here is that I'm not trying to define it as a ref struct but an struct with an opaque pointer so that it's easy to manipulate, compare, hash, and pass to and from p/invokes (the most common operations by far) and because it is going to escape the stack.
SwiftMetadata as is feels like a binding-time type not a runtime type.
I learned early on that we can't rely on it at binding time because of generics. It appears the only thing it gets used for at binding time is putting in a Size value in the StructLayout attribute.
For this I have several questions: 1 - do we need that? 2 - if we do, can't we get it in some other way (for example, follow the steps in the doc'n here.
In the larger picture, we need a binding-time type database, which is it's own thing, but certainly one of the things we can put into in optional properties for Size, Stride, and Alignment so that we can calculate a new Struct's size. But like I said, that's a larger discussion.

Copy link
Member

@jkurdek jkurdek Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 - do we need that?

I think this might be relevant #2796 (comment). tldr: we need to check

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really relevant since we can't know unbound generic type sizes at compile time ever. Having the Size at compile time is only beneficial for frozen structs and frozen enums.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not mean generics, but having an explicit size value in the StructLayout attribute for frozen structs. @matouskozak shared a great example #2796 (comment)

readonly IntPtr handle;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this become SwiftHandle in the future?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great question - since this is a private member and we should never (1) need to access this outside of the TypeMetadata (and I don't think we ever would) and (2) get this type from any other place that as an implicit argument passed to us from Swift or as a return from an explicit call, it's fine to leave it as an IntPtr.


/// <summary>
/// An empty/invalid TypeMetadata object
/// </summary>
public readonly static TypeMetadata Zero = default (TypeMetadata);

/// <summary>
/// Construct a TypeMetadata object
/// </summary>
/// <param name="handle">The handle for the type</param>
TypeMetadata (IntPtr handle)
{
this.handle = handle;
}

/// <summary>
/// Returns true if and only if the TypeMetadata is valid.
/// </summary>
public bool IsValid => handle != IntPtr.Zero;

/// <summary>
/// Throws a NotSupportedException if the TypeMetadata is invalid
/// </summary>
/// <exception cref="NotSupportedException"></exception>
void ThrowOnInvalid ()
{
if (!IsValid)
throw new NotSupportedException ();
}

// This comes from the Swift ABI documentation - https://github.com/swiftlang/swift/blob/23e3f5f5de2ed046f3183264589be1f9a54f7e1e/include/swift/ABI/MetadataValues.h#L117
const long kMaxDiscriminator = 0x7ff;

/// <summary>
/// Returns the kind of this TypeMetadata
/// </summary>
public TypeMetadataKind Kind {
get {
ThrowOnInvalid ();
long val = ReadPointerSizedInt (handle);
if (val == 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there's a mix of spaces and tabs here.

return TypeMetadataKind.None;
if (val > kMaxDiscriminator)
return TypeMetadataKind.Class;
return (TypeMetadataKind)val;
}
}

/// <summary>
/// Reads a pointer sized integer from the location supplied
/// </summary>
/// <param name="p">a pointer to memory</param>
/// <returns></returns>
unsafe static nint ReadPointerSizedInt (IntPtr p)
{
// Check for debug only. This calling code should always do the null
// checking.
#if DEBUG
if (p == IntPtr.Zero)
throw new ArgumentOutOfRangeException (nameof (p));
#endif
return *((nint*)p);
}

/// <summary>
/// Returns true if other is the same as this
/// </summary>
/// <param name="other">a TypeMetadata object to compare</param>
/// <returns>true if the other is the same, false otherwise</returns>
public bool Equals(TypeMetadata other)
{
return other.handle == handle;
}

/// <summary>
/// Returns true if and only if o is a TypeMetadata object and is equal to this
/// </summary>
/// <param name="o">an object to compare</param>
/// <returns>true if the other is the same, false otherwise</returns>
public override bool Equals (object? o)
{
if (o is TypeMetadata tm)
return tm.handle == this.handle;
return false;
}

/// <summary>
/// Returns a hashcode for this TypeMetadata object
/// </summary>
/// <returns>A hashcode for this TypeMetadata object</returns>
public override int GetHashCode ()
{
return handle.GetHashCode ();
}
}