Browse Source

VTable: improve codegen & make it thread safe (#1469)

pull/1470/head
josetr 6 years ago committed by GitHub
parent
commit
5912c236ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 130
      src/Generator/Generators/CSharp/CSharpSources.cs
  2. 24
      src/Runtime/VTables.cs

130
src/Generator/Generators/CSharp/CSharpSources.cs

@ -1583,59 +1583,67 @@ namespace CppSharp.Generators.CSharp
var hasVirtualDtor = wrappedEntries.Any(e => e.Method.IsDestructor); var hasVirtualDtor = wrappedEntries.Any(e => e.Method.IsDestructor);
bool hasDynamicBase = @class.NeedsBase && @class.BaseClass.IsDynamic; bool hasDynamicBase = @class.NeedsBase && @class.BaseClass.IsDynamic;
var originalTableClass = @class.IsDependent ? @class.Specializations[0] : @class; var originalTableClass = @class.IsDependent ? @class.Specializations[0] : @class;
var originalTablePointers = $"new IntPtr[] {{ {string.Join(", ", originalTableClass.Layout.VTablePointers.Select(v => $"*(IntPtr*)({Helpers.InstanceIdentifier} + {v.Offset})"))} }}";
var destructorOnly = "destructorOnly"; var destructorOnly = "destructorOnly";
WriteLine("internal static class VTableLoader"); WriteLine($"internal static{(hasDynamicBase ? " new" : string.Empty)} class VTableLoader");
{ {
WriteOpenBraceAndIndent(); WriteOpenBraceAndIndent();
WriteLine("private static bool initialized;"); WriteLine($"private static volatile bool initialized;");
WriteLine("private static IntPtr[] ManagedVTables;"); WriteLine($"private static readonly IntPtr*[] ManagedVTables = new IntPtr*[{@class.Layout.VTablePointers.Count}];");
WriteLine("private static IntPtr[] Thunks;");
WriteLine("public static CppSharp.Runtime.VTables VTables;");
if (hasVirtualDtor) if (hasVirtualDtor)
WriteLine($"private static IntPtr[] ManagedVTablesDtorOnly;"); WriteLine($"private static readonly IntPtr*[] ManagedVTablesDtorOnly = new IntPtr*[{@class.Layout.VTablePointers.Count}];");
WriteLine("private static readonly global::System.Collections.Generic.List<global::CppSharp.Runtime.SafeUnmanagedMemoryHandle>"); WriteLine($"private static readonly IntPtr[] Thunks = new IntPtr[{wrappedEntries.Count}];");
WriteLineIndent("HandleManagedVTables = new global::System.Collections.Generic.List<global::CppSharp.Runtime.SafeUnmanagedMemoryHandle>();"); WriteLine("private static CppSharp.Runtime.VTables VTables;");
WriteLine($"private static readonly global::System.Collections.Generic.List<CppSharp.Runtime.SafeUnmanagedMemoryHandle>");
WriteLineIndent("SafeHandles = new global::System.Collections.Generic.List<CppSharp.Runtime.SafeUnmanagedMemoryHandle>();");
NewLine();
WriteLine($"public static CppSharp.Runtime.VTables SetupVTables(IntPtr __Instance, bool {destructorOnly} = false)"); WriteLine($"static VTableLoader()");
{ {
WriteOpenBraceAndIndent(); WriteOpenBraceAndIndent();
WriteLine("if (!initialized)"); foreach (var entry in wrappedEntries.Distinct())
{ {
WriteOpenBraceAndIndent(); var name = GetVTableMethodDelegateName(entry.Method);
WriteLine($"Thunks = new IntPtr[{wrappedEntries.Count}];"); WriteLine($"{name + "Instance"} += {name}Hook;");
WriteLine($"var methodDelegates = new Delegate[{originalTableClass.Layout.VTablePointers.Count}][];"); }
for (var i = 0; i < wrappedEntries.Count; ++i)
var uniqueEntries = new HashSet<VTableComponent>(); {
var entry = wrappedEntries[i];
var name = GetVTableMethodDelegateName(entry.Method);
WriteLine($"Thunks[{i}] = Marshal.GetFunctionPointerForDelegate({name + "Instance"});");
}
UnindentAndWriteCloseBrace();
}
NewLine();
for (var i = 0; i < wrappedEntries.Count; ++i) WriteLine($"public static CppSharp.Runtime.VTables SetupVTables(IntPtr instance, bool {destructorOnly} = false)");
{
WriteOpenBraceAndIndent();
WriteLine($"if (!initialized)");
{
WriteOpenBraceAndIndent();
WriteLine($"lock (ManagedVTables)");
WriteOpenBraceAndIndent();
WriteLine($"if (!initialized)");
{ {
var entry = wrappedEntries[i]; WriteOpenBraceAndIndent();
var name = GetVTableMethodDelegateName(entry.Method); WriteLine($"initialized = true;");
if (uniqueEntries.Add(entry)) WriteLine($"VTables.Tables = {($"new IntPtr[] {{ {string.Join(", ", originalTableClass.Layout.VTablePointers.Select(x => $"*(IntPtr*)(instance + {x.Offset})"))} }}")};");
WriteLine($"{name + "Instance"} += {name}Hook;"); WriteLine($"VTables.Methods = new Delegate[{originalTableClass.Layout.VTablePointers.Count}][];");
WriteLine($"Thunks[{i}] = Marshal.GetFunctionPointerForDelegate({name + "Instance"});");
}
NewLine();
WriteLine($"var originalVTables = {originalTablePointers};");
if (hasVirtualDtor)
AllocateNewVTables(@class, wrappedEntries, destructorOnly: true, "ManagedVTablesDtorOnly");
AllocateNewVTables(@class, wrappedEntries, destructorOnly: false, "ManagedVTables"); if (hasVirtualDtor)
AllocateNewVTables(@class, wrappedEntries, destructorOnly: true, "ManagedVTablesDtorOnly");
WriteLine($"initialized = true;"); AllocateNewVTables(@class, wrappedEntries, destructorOnly: false, "ManagedVTables");
WriteLine($"VTables = new CppSharp.Runtime.VTables(originalVTables, methodDelegates);");
if (!hasVirtualDtor) if (!hasVirtualDtor)
{ {
WriteLine($"if ({destructorOnly})"); WriteLine($"if ({destructorOnly})");
WriteLineIndent("return VTables;"); WriteLineIndent("return VTables;");
}
UnindentAndWriteCloseBrace();
} }
UnindentAndWriteCloseBrace();
UnindentAndWriteCloseBrace(); UnindentAndWriteCloseBrace();
} }
NewLine(); NewLine();
@ -1675,7 +1683,7 @@ namespace CppSharp.Generators.CSharp
{{ {{
get {{ get {{
if (__vtables.IsEmpty) if (__vtables.IsEmpty)
__vtables = new CppSharp.Runtime.VTables({originalTablePointers}); __vtables.Tables = {($"new IntPtr[] {{ {string.Join(", ", originalTableClass.Layout.VTablePointers.Select(x => $"*(IntPtr*)({Helpers.InstanceIdentifier} + {x.Offset})"))} }}")};
return __vtables; return __vtables;
}} }}
@ -1701,8 +1709,6 @@ namespace CppSharp.Generators.CSharp
AllocateNewVTablesMS(@class, wrappedEntries, destructorOnly, table); AllocateNewVTablesMS(@class, wrappedEntries, destructorOnly, table);
else else
AllocateNewVTablesItanium(@class, wrappedEntries, destructorOnly, table); AllocateNewVTablesItanium(@class, wrappedEntries, destructorOnly, table);
NewLine();
} }
private void AssignNewVTableEntries(Class @class, string table) private void AssignNewVTableEntries(Class @class, string table)
@ -1710,15 +1716,13 @@ namespace CppSharp.Generators.CSharp
for (int i = 0; i < @class.Layout.VTablePointers.Count; i++) for (int i = 0; i < @class.Layout.VTablePointers.Count; i++)
{ {
var offset = @class.Layout.VTablePointers[i].Offset; var offset = @class.Layout.VTablePointers[i].Offset;
WriteLine($"*(IntPtr*) ({Helpers.InstanceIdentifier} + {offset}) = {table}[{i}];"); WriteLine($"*(IntPtr**)(instance + {offset}) = {table}[{i}];");
} }
} }
private void AllocateNewVTablesMS(Class @class, IList<VTableComponent> wrappedEntries, private void AllocateNewVTablesMS(Class @class, IList<VTableComponent> wrappedEntries,
bool destructorOnly, string table) bool destructorOnly, string table)
{ {
WriteLine($"{table} = new IntPtr[{@class.Layout.VFTables.Count}];");
for (int i = 0; i < @class.Layout.VFTables.Count; i++) for (int i = 0; i < @class.Layout.VFTables.Count; i++)
{ {
VFTableInfo vftable = @class.Layout.VFTables[i]; VFTableInfo vftable = @class.Layout.VFTables[i];
@ -1734,8 +1738,6 @@ namespace CppSharp.Generators.CSharp
private void AllocateNewVTablesItanium(Class @class, IList<VTableComponent> wrappedEntries, private void AllocateNewVTablesItanium(Class @class, IList<VTableComponent> wrappedEntries,
bool destructorOnly, string table) bool destructorOnly, string table)
{ {
WriteLine($"{table} = new IntPtr[1];");
AllocateNewVTableEntries(@class.Layout.Layout.Components, AllocateNewVTableEntries(@class.Layout.Layout.Components,
wrappedEntries, @class.Layout.VTablePointers[0].Offset, 0, wrappedEntries, @class.Layout.VTablePointers[0].Offset, 0,
VTables.ItaniumOffsetToTopAndRTTI, destructorOnly, table); VTables.ItaniumOffsetToTopAndRTTI, destructorOnly, table);
@ -1747,31 +1749,13 @@ namespace CppSharp.Generators.CSharp
{ {
string suffix = (destructorOnly ? "_dtor" : string.Empty) + string suffix = (destructorOnly ? "_dtor" : string.Empty) +
(tableIndex == 0 ? string.Empty : tableIndex.ToString(CultureInfo.InvariantCulture)); (tableIndex == 0 ? string.Empty : tableIndex.ToString(CultureInfo.InvariantCulture));
int size = entries.Count;
uint pointerSize = Context.TargetInfo.PointerWidth / 8; WriteLine($"{table}[{tableIndex}] = CppSharp.Runtime.VTables.CloneTable(SafeHandles, instance, {vptrOffset}, {entries.Count});");
string vtptr = $"vtptr{suffix}";
// allocate memory for the new v-table
WriteLine($"var {vtptr} = Marshal.AllocHGlobal({size} * {pointerSize});");
// ensure it's automatically freed when no longer needed
WriteLine($"HandleManagedVTables.Add(new global::CppSharp.Runtime.SafeUnmanagedMemoryHandle({vtptr}, true));");
string vfptr = $"vfptr{suffix}";
// obtain a pointer in the table to the start of virtual functions
WriteLine($"var {vfptr} = {vtptr} + {offsetRTTI} * {pointerSize};");
WriteLine($"{table}[{tableIndex}] = {vfptr};");
if (!destructorOnly)
WriteLine($"methodDelegates[{tableIndex}] = new Delegate[{entries.Count}];");
// fill the newly allocated v-table // fill the newly allocated v-table
for (var i = 0; i < entries.Count; i++) for (var i = 0; i < entries.Count; i++)
{ {
var entry = entries[i]; var entry = entries[i];
var offset = pointerSize * (i - offsetRTTI);
var nativeVftableEntry = $@"*(IntPtr*) ((*(IntPtr*) {
Helpers.InstanceIdentifier}) + {vptrOffset} + {offset})";
var managedVftableEntry = $"*(IntPtr*) ({vfptr} + {offset})";
if ((entry.Kind == VTableComponentKind.FunctionPointer || if ((entry.Kind == VTableComponentKind.FunctionPointer ||
entry.Kind == VTableComponentKind.DeletingDtorPointer) && entry.Kind == VTableComponentKind.DeletingDtorPointer) &&
@ -1779,11 +1763,11 @@ namespace CppSharp.Generators.CSharp
(!destructorOnly || entry.Method.IsDestructor || (!destructorOnly || entry.Method.IsDestructor ||
Context.Options.ExplicitlyPatchedVirtualFunctions.Contains(entry.Method.QualifiedOriginalName))) Context.Options.ExplicitlyPatchedVirtualFunctions.Contains(entry.Method.QualifiedOriginalName)))
// patch with pointers to managed code where needed // patch with pointers to managed code where needed
WriteLine("{0} = Thunks[{1}];", managedVftableEntry, wrappedEntries.IndexOf(entry)); WriteLine("{0}[{1}][{2}] = Thunks[{3}];", table, tableIndex, i - offsetRTTI, wrappedEntries.IndexOf(entry));
else
// and simply copy the rest
WriteLine("{0} = {1};", managedVftableEntry, nativeVftableEntry);
} }
if (!destructorOnly)
WriteLine($"VTables.Methods[{tableIndex}] = new Delegate[{entries.Count}];");
} }
private void GenerateVTableClassSetupCall(Class @class, bool destructorOnly = false) private void GenerateVTableClassSetupCall(Class @class, bool destructorOnly = false)

24
src/Runtime/VTables.cs

@ -1,14 +1,15 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace CppSharp.Runtime namespace CppSharp.Runtime
{ {
public unsafe struct VTables public struct VTables
{ {
public Delegate[][] Methods { get; } public Delegate[][] Methods { get; set; }
public IntPtr[] Tables { get; } public IntPtr[] Tables { get; set; }
private Dictionary<(short, short, int), Delegate> Specializations; private ConcurrentDictionary<(short, short, int), Delegate> Specializations;
public VTables(IntPtr[] tables, Delegate[][] methods = null) public VTables(IntPtr[] tables, Delegate[][] methods = null)
{ {
@ -34,7 +35,7 @@ namespace CppSharp.Runtime
else else
{ {
if (Specializations == null) if (Specializations == null)
Specializations = new Dictionary<(short, short, int), Delegate>(); Specializations = new ConcurrentDictionary<(short, short, int), Delegate>();
var key = (specialiation, table, slot); var key = (specialiation, table, slot);
@ -44,5 +45,16 @@ namespace CppSharp.Runtime
return (T)method; return (T)method;
} }
} }
public unsafe static IntPtr* CloneTable(List<SafeUnmanagedMemoryHandle> cache, IntPtr instance, int offset, int size)
{
var sizeInBytes = size * sizeof(IntPtr);
var src = ((*(IntPtr*)instance) + offset).ToPointer();
var entries = (IntPtr*)Marshal.AllocHGlobal(sizeInBytes);
Buffer.MemoryCopy(src, entries, sizeInBytes, sizeInBytes);
cache.Add(new SafeUnmanagedMemoryHandle((IntPtr)entries, true));
return entries;
}
} }
} }

Loading…
Cancel
Save