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] Add support for non-frozen structs #2870

Merged
merged 7 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ msbuild.wrn
.libs/
*.lo
*.o
build/
kotlarmilos marked this conversation as resolved.
Show resolved Hide resolved

# Cross directory
eng/common/cross
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public bool Handles(BaseDecl decl)
/// <summary>
/// Constructs a new instance of ModuleHandler.
/// </summary>
public IModuleHandler Construct ()
public IModuleHandler Construct()
{
return new ModuleHandler();
}
Expand All @@ -34,17 +34,22 @@ public IModuleHandler Construct ()
/// </summary>
public class ModuleHandler : BaseHandler, IModuleHandler
{
public ModuleHandler ()
public ModuleHandler()
{
}

/// <summary>
/// Marshals the module declaration.
/// </summary>
/// <param name="moduleDecl">The module declaration.</param>
public IEnvironment Marshal(BaseDecl moduleDecl)
/// <param name="typeDatabase">The type database instance.</param>
public IEnvironment Marshal(BaseDecl decl, TypeDatabase typeDatabase)
{
return new ModuleEnvironment(moduleDecl);
if (decl is not ModuleDecl moduleDecl)
{
throw new ArgumentException("The provided decl must be a ModuleDecl.", nameof(decl));
}
return new ModuleEnvironment(moduleDecl, typeDatabase);
}

/// <summary>
Expand All @@ -54,11 +59,11 @@ public IEnvironment Marshal(BaseDecl moduleDecl)
/// <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)
public void Emit(IndentedTextWriter writer, IEnvironment env, Conductor conductor)
{
var moduleEnv = (ModuleEnvironment)env;
var moduleDecl = (ModuleDecl)moduleEnv.ModuleDecl;
var moduleDecl = moduleEnv.ModuleDecl;

var generatedNamespace = $"Swift.{moduleDecl.Name}";

writer.WriteLine($"using System;");
Expand All @@ -82,18 +87,18 @@ public void Emit(IndentedTextWriter writer, IEnvironment env, Conductor conducto
writer.WriteLine($"public class {moduleDecl.Name}");
writer.WriteLine("{");
writer.Indent++;
foreach(FieldDecl fieldDecl in moduleDecl.Fields)
foreach (FieldDecl fieldDecl in moduleDecl.Fields)
{
string accessModifier = fieldDecl.Visibility == Visibility.Public ? "public" : "private";
writer.WriteLine($"{accessModifier} {fieldDecl.CSTypeIdentifier.Name} {fieldDecl.Name};");
}
writer.WriteLine();
foreach(MethodDecl methodDecl in moduleDecl.Methods)
foreach (MethodDecl methodDecl in moduleDecl.Methods)
{
if (conductor.TryGetMethodHandler(methodDecl, out var methodHandler))
{
var methodEnv = methodHandler.Marshal(methodDecl);
methodHandler.Emit(writer, methodEnv, conductor, typeDatabase);
var methodEnv = methodHandler.Marshal(methodDecl, env.TypeDatabase);
methodHandler.Emit(writer, methodEnv, conductor);
}
else
{
Expand All @@ -108,7 +113,7 @@ public void Emit(IndentedTextWriter writer, IEnvironment env, Conductor conducto
}

// Emit top-level types
base.HandleBaseDecl(writer, moduleDecl.Declarations, conductor, typeDatabase);
base.HandleBaseDecl(writer, moduleDecl.Declarations, conductor, env.TypeDatabase);

writer.Indent--;
writer.WriteLine("}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,49 @@
namespace BindingsGeneration
{
/// <summary>
/// Factory class for creating instances of StructHandler.
/// Factory class for creating instances of FrozenStructHandler.
/// </summary>
public class StructHandlerFactory : IFactory<BaseDecl, ITypeHandler>
public class FrozenStructHandlerFactory : IFactory<BaseDecl, ITypeHandler>
{
/// <summary>
/// <summary>
/// Determines if the factory handles the specified declaration.
/// </summary>
/// <param name="decl">The base declaration.</param>
public bool Handles(BaseDecl decl)
{
return decl is StructDecl;
return decl is StructDecl structDecl && MarshallingHelpers.StructIsMarshalledAsCSStruct(structDecl);
}

/// <summary>
/// Constructs a new instance of StructHandler.
/// </summary>
public ITypeHandler Construct ()
public ITypeHandler Construct()
{
return new StructHandler();
return new FrozenStructHandler();
}
}

/// <summary>
/// Handler class for struct declarations.
/// Handler class for frozen struct declarations.
/// </summary>
public class StructHandler : BaseHandler, ITypeHandler
public class FrozenStructHandler : BaseHandler, ITypeHandler
{
public StructHandler ()
public FrozenStructHandler()
{
}

/// <summary>
/// Marshals the specified struct declaration.
/// </summary>
/// <param name="structDecl">The struct declaration.</param>
public IEnvironment Marshal(BaseDecl structDecl)
/// <param name="typeDatabase">The type database instance.</param>
public IEnvironment Marshal(BaseDecl decl, TypeDatabase typeDatabase)
{
return new TypeEnvironment(structDecl);
if (decl is not StructDecl structDecl)
{
throw new ArgumentException("The provided decl must be a StructDecl.", nameof(decl));
}
return new TypeEnvironment(structDecl, typeDatabase);
}

/// <summary>
Expand All @@ -54,14 +59,14 @@ public IEnvironment Marshal(BaseDecl structDecl)
/// <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)
public void Emit(IndentedTextWriter writer, IEnvironment env, Conductor conductor)
{
var structEnv = (TypeEnvironment)env;
var structDecl = (StructDecl)structEnv.TypeDecl;
var parentDecl = structDecl.ParentDecl ?? throw new ArgumentNullException(nameof(structDecl.ParentDecl));
var moduleDecl = structDecl.ModuleDecl ?? throw new ArgumentNullException(nameof(structDecl.ParentDecl));
// Retrieve type info from the type database
var typeRecord = typeDatabase.Registrar.GetType(moduleDecl.Name, structDecl.Name);
var typeRecord = env.TypeDatabase.Registrar.GetType(moduleDecl.Name, structDecl.Name);
SwiftTypeInfo? swiftTypeInfo = typeRecord?.SwiftTypeInfo;

if (swiftTypeInfo.HasValue)
Expand Down Expand Up @@ -91,7 +96,7 @@ public void Emit(IndentedTextWriter writer, IEnvironment env, Conductor conducto
}
writer.WriteLine();

base.HandleBaseDecl(writer, structDecl.Declarations, conductor, typeDatabase);
base.HandleBaseDecl(writer, structDecl.Declarations, conductor, env.TypeDatabase);

writer.Indent--;
writer.WriteLine("}");
Expand Down Expand Up @@ -126,6 +131,152 @@ private unsafe bool VerifyFieldRecord(SwiftTypeInfo swiftTypeInfo, int fieldInde
}
}

/// <summary>
/// Factory class for creating instances of NonFrozenStructHandler.
/// </summary>
public class NonFrozenStructHandlerFactory : IFactory<BaseDecl, ITypeHandler>
{
/// <summary>
/// Determines if the factory handles the specified declaration.
/// </summary>
/// <param name="decl">The base declaration.</param>
public bool Handles(BaseDecl decl)
{
return decl is StructDecl structDecl && !MarshallingHelpers.StructIsMarshalledAsCSStruct(structDecl);
}

/// <summary>
/// Constructs a new instance of StructHandler.
/// </summary>
public ITypeHandler Construct()
{
return new NonFrozenStructHandler();
}
}

/// <summary>
/// Handler class for non-frozen struct declarations.
/// </summary>
public class NonFrozenStructHandler : BaseHandler, ITypeHandler
{
public NonFrozenStructHandler()
{
}

/// <summary>
/// Marshals the specified struct declaration.
/// </summary>
/// <param name="structDecl">The struct declaration.</param>
/// <param name="typeDatabase">The type database instance.</param>
public IEnvironment Marshal(BaseDecl decl, TypeDatabase typeDatabase)
{
if (decl is not StructDecl structDecl)
{
throw new ArgumentException("The provided decl must be a StructDecl.", nameof(decl));

}
return new TypeEnvironment(structDecl, typeDatabase);
}

/// <summary>
/// Emits the code for the specified environment.
/// </summary>
/// <param name="writer">The IndentedTextWriter instance.</param>
/// <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)
{
var structEnv = (TypeEnvironment)env;
var structDecl = (StructDecl)structEnv.TypeDecl;
var parentDecl = structDecl.ParentDecl ?? throw new ArgumentNullException(nameof(structDecl.ParentDecl));
var moduleDecl = structDecl.ModuleDecl ?? throw new ArgumentNullException(nameof(structDecl.ModuleDecl));
var typeRecord = env.TypeDatabase.Registrar.GetType(moduleDecl.Name, structDecl.Name);
SwiftTypeInfo? swiftTypeInfo = typeRecord?.SwiftTypeInfo;

writer.WriteLine($"public unsafe class {structDecl.Name} : IDisposable");
writer.WriteLine("{");
writer.Indent++;

WritePrivateFields(writer);
WriteDisposeMethod(writer, structDecl.Name);
WriteFinalizer(writer, structDecl.Name);
WritePayload(writer);
WriteMetadata(writer, moduleDecl.Name, structDecl.MangledName, env.TypeDatabase);

writer.WriteLine();
base.HandleBaseDecl(writer, structDecl.Declarations, conductor, env.TypeDatabase);

writer.Indent--;
writer.WriteLine("}");
}

/// <summary>
/// Writes the private fields for the class.
/// </summary>
private static void WritePrivateFields(IndentedTextWriter writer)
{
writer.WriteLine();
writer.WriteLine("private static nuint _payloadSize = Metadata.Size;");
writer.WriteLine("private SwiftHandle _payload = SwiftHandle.Zero;");
writer.WriteLine("private bool _disposed = false;");
}

/// <summary>
/// Writes the Dispose method for the class.
/// </summary>
private static void WriteDisposeMethod(IndentedTextWriter writer, string className)
jkurdek marked this conversation as resolved.
Show resolved Hide resolved
{
writer.WriteLine("public void Dispose()");
writer.WriteLine("{");
writer.Indent++;
writer.WriteLine("if (!_disposed)");
writer.WriteLine("{");
writer.Indent++;
writer.WriteLine("NativeMemory.Free((void*)_payload);");
writer.WriteLine("_disposed = true;");
writer.WriteLine("GC.SuppressFinalize(this);");
writer.Indent--;
writer.WriteLine("}");
writer.Indent--;
writer.WriteLine("}");
}

/// <summary>
/// Writes the finalizer for the class.
/// </summary>
private static void WriteFinalizer(IndentedTextWriter writer, string className)
{
writer.WriteLine($"~{className}()");
writer.WriteLine("{");
writer.Indent++;
writer.WriteLine("NativeMemory.Free((void*)_payload);");
writer.Indent--;
writer.WriteLine("}");
}

/// <summary>
/// Writes the metadata for the class.
/// </summary>
private static void WriteMetadata(IndentedTextWriter writer, string moduleName, string mangledName, TypeDatabase typeDatabase)
{
writer.WriteLine("public static TypeMetadata Metadata => PInvoke_getMetadata();");

writer.WriteLine("[UnmanagedCallConv(CallConvs = new Type[] { typeof(CallConvSwift) })]");
string libPath = typeDatabase.GetLibraryName(moduleName);
writer.WriteLine($"[DllImport(\"{libPath}\", EntryPoint = \"{mangledName}Ma\")]");
writer.WriteLine("internal static extern TypeMetadata PInvoke_getMetadata();");
}

/// <summary>
/// Writes the payload accessor for the class.
/// </summary>
private static void WritePayload(IndentedTextWriter writer)
{
writer.WriteLine("public SwiftHandle Payload => _payload;");
}
}

/// <summary>
/// Factory class for creating instances of ClassHandler.
/// </summary>
Expand All @@ -143,7 +294,7 @@ public bool Handles(BaseDecl decl)
/// <summary>
/// Constructs a new instance of ClassHandler.
/// </summary>
public ITypeHandler Construct ()
public ITypeHandler Construct()
{
return new ClassHandler();
}
Expand All @@ -154,17 +305,22 @@ public ITypeHandler Construct ()
/// </summary>
public class ClassHandler : BaseHandler, ITypeHandler
{
public ClassHandler ()
public ClassHandler()
{
}

/// <summary>
/// <summary>
/// Marshals the specified class declaration.
/// </summary>
/// <param name="classDecl">The class declaration.</param>
public IEnvironment Marshal(BaseDecl classDecl)
/// <param name="typeDatabase">The type database instance.</param>
public IEnvironment Marshal(BaseDecl decl, TypeDatabase typeDatabase)
{
return new TypeEnvironment(classDecl);
if (decl is not ClassDecl classDecl)
{
throw new ArgumentException("The provided decl must be a ClassDecl.", nameof(decl));
}
return new TypeEnvironment(classDecl, typeDatabase);
}

/// <summary>
Expand All @@ -174,15 +330,15 @@ public IEnvironment Marshal(BaseDecl classDecl)
/// <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)
public void Emit(IndentedTextWriter writer, IEnvironment env, Conductor conductor)
{
var classEnv = (TypeEnvironment)env;
var classDecl = (ClassDecl)classEnv.TypeDecl;

writer.WriteLine($"public unsafe class {classDecl.Name} {{");
writer.Indent++;

base.HandleBaseDecl(writer, classDecl.Declarations, conductor, typeDatabase);
base.HandleBaseDecl(writer, classDecl.Declarations, conductor, env.TypeDatabase);

writer.Indent--;
writer.WriteLine("}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ public void EmitModule(ModuleDecl moduleDecl)
IndentedTextWriter writer = new(sw);
var @namespace = $"Swift.{moduleDecl.Name}";

var env = moduleHandler.Marshal(moduleDecl);
moduleHandler.Emit(writer, env, _conductor, _typeDatabase);
var env = moduleHandler.Marshal(moduleDecl, _typeDatabase);
moduleHandler.Emit(writer, env, _conductor);

string csOutputPath = Path.Combine(_outputDirectory, $"{@namespace}.cs");
using (StreamWriter outputFile = new(csOutputPath))
Expand Down
Loading