-
Notifications
You must be signed in to change notification settings - Fork 205
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] Add support for non-frozen structs #2870
Conversation
decl = CreateClassDecl(node, parentDecl, moduleDecl); | ||
} | ||
// TODO: Find better place for this | ||
typeRecord.IsBlittable = !swiftTypeInfo.ValueWitnessTable->IsNonPOD || !swiftTypeInfo.ValueWitnessTable->IsNonBitwiseTakable; //TODO: This is not the full picture. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deciding on whether a struct should be projected as a class or struct on c# side is more complex than that. We will have to implement a correct algorithm for that. I guess for now the question is where this should live. ABIParsing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's not part of the actual parsing, my inclination is no.
Measuring the size, stride, and alignment and determining blittability should be a post processing step and should end up in the type database entry for the type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that metadata info should live in the type database. I suggest to retrieve it using MachO reader, but I think it is important to have it at parsing time. Do we want to read .dylib before the ABI parsing to consume information for projections from type database?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that this information should end up in TypeDatabase
. But I think it also needs to be present on a TypeDecl
, otherwise handling each TypeDecl
would require reading TypeDatabase
entry.
Do we want to read .dylib before the ABI parsing to consume information for projections from type database?
I am not sure I understand
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Populate type database using MachO reader before ABI parsing, so metadata information can be consumed without calling into dylib from HandleTypeDecl
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was looking into how to do this and it's a little more complicated than you think. The problem is that the value witness table for any particular type might not live in the dylib that you're reading. For example, if I define a swift type like this:
public struct NoInThisDylib {
public let contents: Int
public init(with: Int) {
contents = with
}
}
Then the value witness table for this type is an external reference to the value witness table for Int64 from libswiftCore.dylib. IIRC all heap allocated types (classes) share the same value witness table.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// <param name="env">The environment.</param> | ||
/// <param name="conductor">The conductor instance.</param> | ||
/// <param name="typeDatabase">The type database instance.</param> | ||
public void Emit(IndentedTextWriter writer, IEnvironment env, Conductor conductor, TypeDatabase typeDatabase) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TypeDatabase will be most likely used in majority of the handlers. Maybe it would make sense to inject it into every handler during their construction
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should live in the environment, I suspect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is TypeDatabase
a singleton?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any reason to not have TypeDatabase
as singleton. Ideally it should be immutable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added TypeDatabase
to env
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great start. I will come back and look at this more closely later. I wanted to provide immediate feedback so you could start thinking about changes you might need.
src/Swift.Bindings/src/Emitter/StringCSharpEmitter/Handler/MethodHandler.cs
Outdated
Show resolved
Hide resolved
writer.WriteLine("{"); | ||
writer.Indent++; | ||
|
||
string PInvokeName = $"{methodEnv.PInvokePrefix}{methodDecl.Name}"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming methods is going to be very common. I'd like to see naming centralized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, especially with PInvoke methods (i.e. multiple constructors)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is (at least) two types of naming we need to handle:
- Naming things projected from Swift - some things in Swift are unpronouncable (e.g. emoji names) in csharp. We need to do the translation.
- Generating names for extra things we need - pinvokes, proxies etc.
When talking about centralizing naming do we want to have one thing doing 1. and 2. ?
Edit: Number one could probably be done at parsing stage and encoded in the TypeDatabase
. So this should leave only number 2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming things projected from Swift - some things in Swift are unpronouncable (e.g. emoji names) in csharp. We need to do the translation.
I suggest creating a tracking issue and deferring this to a later stage. Do we want to support operator overloading?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a third case of naming, which is local variables used in marshaling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/Swift.Bindings/src/Emitter/StringCSharpEmitter/Handler/MethodHandler.cs
Outdated
Show resolved
Hide resolved
List<ArgumentDecl> tempDecl = new(methodDecl.CSSignature); | ||
foreach (var argument in MethodDecl.CSSignature.Skip(1)) | ||
{ | ||
var typeRecord = TypeDatabase.Registrar.GetType(argument); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a TryGet pattern since the type may not exist. In addition, if the type database entry comes from an external module, you will either need to use the full type name of inject a using statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should always use fully qualified names - to avoid unintentional collisions. In fact we should use the global:
prefix as well to make sure we don't pick up some other random namespace item. Just like Roslyn source generator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stephen-hawley, I'm thinking about the behaviour on TryGet method failure. Should we just skip this methods projection? Or we should risk getting it wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In BTfS for this class of failure it was treated as a warning and a skip for the method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in order to properly skip the method we need to check all the conditions before we write any code. (this is particularly important if code generation is non-linear - If I encounter problem writing the pinvoke I want to cancel writing the wrapper as well)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could do this by creating something like "transactions" - when we're about to start projecting a method, we "start a transaction" which will reset all output buffers. Then we project/write and everything goes into output buffers, only when we're successfully done, we "commit" and appends the buffers to the actual output.
This would be more robust than trying to check for every error condition upfront - it's easy to miss something, and that doesn't count actual unexpected exceptions during the writing process (our bugs and so on). It would be really nice if the tool was resilient to its own bugs to some degree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if the type database entry comes from an external module, you will either need to use the full type name of inject a using statement.
I agree. I think we should add some mechanism which allows us to resolve a fully qualified c# name from any Decl
and any TypeDatabase entry. This should probably be bundled with the translation of unpronouncable names is csharp. So lets track it together. Out of scope of this PR. #2889
This should be a TryGet pattern since the type may not exist.
I like the "transactional" idea. Again I think it will require some separate discussion. Lets do it separately. Tracking issue: #2890.
/// <param name="env">The environment.</param> | ||
/// <param name="conductor">The conductor instance.</param> | ||
/// <param name="typeDatabase">The type database instance.</param> | ||
public void Emit(IndentedTextWriter writer, IEnvironment env, Conductor conductor, TypeDatabase typeDatabase) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is TypeDatabase
a singleton?
src/Swift.Bindings/src/Emitter/StringCSharpEmitter/Handler/MethodHandler.cs
Outdated
Show resolved
Hide resolved
writer.WriteLine("{"); | ||
writer.Indent++; | ||
|
||
string PInvokeName = $"{methodEnv.PInvokePrefix}{methodDecl.Name}"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, especially with PInvoke methods (i.e. multiple constructors)
return decl is StructDecl structDecl && structDecl.IsFrozen && structDecl.IsBlittable; | ||
} | ||
|
||
public static TypeRecord GetType(this TypeRegistrar typeRegistrar, ArgumentDecl argumentDecl) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, this should be part of type database. I think we should create a clear type database interface as it will be used across all components of the tooling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. Currently type database accepts queries for types using module name and type name. I believe a lot of the queries will be driven by looking at some typeSpec, so maybe we can include that in the interface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's something to put a pin in: Swift is simple in that there is only one namespace: the module name. C# not so much. In either case, it might be handy to have something like this:
public class ClassName { // implement IEquatable<> etc.
public ClassName(string namespace, string name)
{
Namespace = namespace;
Name = name;
FullName = Namespace + "." + Name;
}
public string Namespace { get; init; }
public string Name { get; init; } // may include inner classes as a path
public string FullName { get; init }
}
And use this wherever you want to refer to either a C# class or a Swift class.
When writing code non-linearly, we can do namespaces.AddIfNotPresent(clname.Namespace)
or imports.AddIfNotPresent(clname.Namespace)
.
src/Swift.Bindings/src/Emitter/StringCSharpEmitter/Handler/TypeHandler.cs
Show resolved
Hide resolved
decl = CreateClassDecl(node, parentDecl, moduleDecl); | ||
} | ||
// TODO: Find better place for this | ||
typeRecord.IsBlittable = !swiftTypeInfo.ValueWitnessTable->IsNonPOD || !swiftTypeInfo.ValueWitnessTable->IsNonBitwiseTakable; //TODO: This is not the full picture. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that metadata info should live in the type database. I suggest to retrieve it using MachO reader, but I think it is important to have it at parsing time. Do we want to read .dylib before the ABI parsing to consume information for projections from type database?
_assemblyPath = TestsHelper.Compile( | ||
new string[] { "NonFrozenStructs/*.cs" }, | ||
new string[] { }, | ||
new string[] { }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Indentation
34b4a19
to
462b0a1
Compare
462b0a1
to
5fd8181
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Thanks!
Contributes to #2822
This PR brings some improvements to projecting structs
TODO:
Simplify testing