Generate VTable class (#296)
* Allow custom VTable implementations * Optionally generate VTable * Enable VTable generation globally * Distinct Entrypoints * Add Swap overload
This commit is contained in:
Родитель
0fe87ad2d5
Коммит
1781f16d78
|
@ -47,7 +47,10 @@ resharper_web_config_module_not_resolved_highlighting=warning
|
|||
resharper_web_config_type_not_resolved_highlighting=warning
|
||||
resharper_web_config_wrong_module_highlighting=warning
|
||||
|
||||
# Silk Touch options
|
||||
silk_touch_vtable_generate=true
|
||||
|
||||
[*.{appxmanifest,asax,ascx,aspx,build,config,cs,cshtml,csproj,dbml,discomap,dtd,fs,fsi,fsscript,fsx,htm,html,jsproj,lsproj,master,ml,mli,njsproj,nuspec,proj,props,razor,resw,resx,skin,StyleCop,targets,tasks,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
|
||||
indent_style=space
|
||||
indent_size=4
|
||||
tab_width=4
|
||||
tab_width=4
|
|
@ -14,13 +14,13 @@ namespace Silk.NET.Core.Native
|
|||
private readonly INativeContext _ctx;
|
||||
private IVTable _vTable;
|
||||
|
||||
protected NativeApiContainer(INativeContext ctx, IVTable table, bool tableInitialized = false)
|
||||
protected NativeApiContainer(INativeContext ctx)
|
||||
{
|
||||
_ctx = ctx;
|
||||
_vTable = table;
|
||||
// Virtual member call should be fine unless we have a rogue implementer
|
||||
// The only implementer of this function should be SilkTouch
|
||||
// ReSharper disable VirtualMemberCallInConstructor
|
||||
_vTable = CreateVTable();
|
||||
var slotCount = CoreGetSlotCount();
|
||||
if (slotCount == 0)
|
||||
{
|
||||
|
@ -30,15 +30,11 @@ namespace Silk.NET.Core.Native
|
|||
"This could be because of a SilkTouch bug, or because you're not using SilkTouch at all."
|
||||
);
|
||||
}
|
||||
if (!tableInitialized)
|
||||
_vTable.Initialize(_ctx, slotCount);
|
||||
_vTable.Initialize(_ctx, slotCount);
|
||||
GcUtility = new GcUtility(1, CoreGcSlotCount());
|
||||
// ReSharper restore VirtualMemberCallInConstructor
|
||||
}
|
||||
|
||||
protected NativeApiContainer(INativeContext ctx) : this(ctx, new ConcurrentDictionaryVTable(), false)
|
||||
{ }
|
||||
|
||||
public GcUtility GcUtility { get; }
|
||||
|
||||
public IVTable CurrentVTable => _vTable;
|
||||
|
@ -51,14 +47,9 @@ namespace Silk.NET.Core.Native
|
|||
|
||||
protected virtual int CoreGetSlotCount() => 0;
|
||||
protected virtual int CoreGcSlotCount() => 0;
|
||||
|
||||
protected IVTable SwapVTable(IVTable newTable, bool initialized = false)
|
||||
{
|
||||
if (!initialized)
|
||||
newTable.Initialize(_ctx, CoreGetSlotCount());
|
||||
|
||||
return Interlocked.Exchange(ref _vTable, newTable);
|
||||
}
|
||||
protected virtual IVTable CreateVTable() => new ConcurrentDictionaryVTable();
|
||||
protected IVTable SwapVTable() => Interlocked.Exchange(ref _vTable, CreateVTable());
|
||||
protected IVTable SwapVTable(IVTable newVTable) => Interlocked.Exchange(ref _vTable, newVTable);
|
||||
|
||||
protected void Pin(object o, int slot = -1)
|
||||
{
|
||||
|
|
|
@ -30,21 +30,23 @@ namespace Silk.NET.SilkTouch
|
|||
public void Execute(SourceGeneratorContext context)
|
||||
{
|
||||
MarshalBuilder marshalBuilder;
|
||||
|
||||
if (!context.Compilation.ReferencedAssemblyNames.Any(ai => ai.Name.Equals("Silk.NET.Core", StringComparison.OrdinalIgnoreCase)))
|
||||
|
||||
if (!context.Compilation.ReferencedAssemblyNames.Any
|
||||
(ai => ai.Name.Equals("Silk.NET.Core", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(Diagnostics.SilkNetCoreMissing, Location.None));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!(context.SyntaxReceiver is SyntaxReceiver receiver))
|
||||
return;
|
||||
|
||||
var nativeApiAttribute = context.Compilation.GetTypeByMetadataName("Silk.NET.Core.Native.NativeApiAttribute");
|
||||
|
||||
var nativeApiAttribute = context.Compilation.GetTypeByMetadataName
|
||||
("Silk.NET.Core.Native.NativeApiAttribute");
|
||||
|
||||
if (nativeApiAttribute is null)
|
||||
return;
|
||||
|
||||
|
||||
marshalBuilder = new MarshalBuilder();
|
||||
|
||||
marshalBuilder.Use(ParameterInitMiddleware);
|
||||
|
@ -62,10 +64,7 @@ namespace Silk.NET.SilkTouch
|
|||
try
|
||||
{
|
||||
var s = ProcessClassDeclaration
|
||||
(
|
||||
receiverClassDeclaration, context, nativeApiAttribute, marshalBuilder,
|
||||
ref processedSymbols
|
||||
);
|
||||
(receiverClassDeclaration, context, nativeApiAttribute, marshalBuilder, ref processedSymbols);
|
||||
|
||||
if (s is null) continue;
|
||||
|
||||
|
@ -78,7 +77,8 @@ namespace Silk.NET.SilkTouch
|
|||
{
|
||||
context.ReportDiagnostic
|
||||
(
|
||||
Diagnostic.Create(Diagnostics.ProcessClassFailure, receiverClassDeclaration.GetLocation(), ex.ToString())
|
||||
Diagnostic.Create
|
||||
(Diagnostics.ProcessClassFailure, receiverClassDeclaration.GetLocation(), ex.ToString())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ namespace Silk.NET.SilkTouch
|
|||
|
||||
if (!namespaceDeclaration.Parent.IsKind(SyntaxKind.CompilationUnit))
|
||||
return null;
|
||||
|
||||
|
||||
var compilationUnit = (CompilationUnitSyntax) namespaceDeclaration.Parent;
|
||||
|
||||
var classSymbol = ModelExtensions.GetDeclaredSymbol
|
||||
|
@ -114,6 +114,16 @@ namespace Silk.NET.SilkTouch
|
|||
(classSymbol, compilation.GetTypeByMetadataName("Silk.NET.Core.Native.NativeApiContainer")))
|
||||
return null;
|
||||
|
||||
var generateVTable = false;
|
||||
|
||||
if (sourceContext.AnalyzerConfigOptions.GetOptions
|
||||
(classDeclaration.SyntaxTree)
|
||||
.TryGetValue("silk_touch_vtable_generate", out var genvtablestr))
|
||||
{
|
||||
if (bool.TryParse(genvtablestr, out var v))
|
||||
generateVTable = v;
|
||||
}
|
||||
|
||||
var classAttribute = classSymbol.GetAttributes()
|
||||
.FirstOrDefault(x => SymbolEqualityComparer.Default.Equals(x.AttributeClass, nativeApiAttributeSymbol));
|
||||
|
||||
|
@ -144,7 +154,11 @@ namespace Silk.NET.SilkTouch
|
|||
))
|
||||
)
|
||||
.Select(x => (x.declaration, x.symbol, ToNativeApiAttribute(x.attribute)))
|
||||
.Where(x => x.declaration.Modifiers.Any(x2 => x2.IsKind(SyntaxKind.PartialKeyword)) && x.symbol.PartialImplementationPart is null)
|
||||
.Where
|
||||
(
|
||||
x => x.declaration.Modifiers.Any
|
||||
(x2 => x2.IsKind(SyntaxKind.PartialKeyword)) && x.symbol.PartialImplementationPart is null
|
||||
)
|
||||
.Select
|
||||
(
|
||||
x => (declaration: x.declaration, symbol: x.symbol,
|
||||
|
@ -152,6 +166,7 @@ namespace Silk.NET.SilkTouch
|
|||
callingConvention: NativeApiAttribute.GetCallingConvention(x.Item3, classNativeApiAttribute))
|
||||
)
|
||||
.ToArray();
|
||||
List<string> entryPoints = new List<string>();
|
||||
foreach (var (declaration, symbol, entryPoint, callingConvention) in methods)
|
||||
{
|
||||
try
|
||||
|
@ -163,6 +178,22 @@ namespace Silk.NET.SilkTouch
|
|||
// this is terminal, we never call next
|
||||
|
||||
var parameters = ctx.ResolveAllLoadParameters();
|
||||
|
||||
var fPtrType = FunctionPointerType
|
||||
(
|
||||
Identifier(GetCallingConvention(callingConvention)),
|
||||
SeparatedList
|
||||
(
|
||||
ctx.LoadTypes.Select
|
||||
(
|
||||
x => Parameter
|
||||
(Identifier(x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)))
|
||||
)
|
||||
)
|
||||
);
|
||||
if (!entryPoints.Contains(entryPoint))
|
||||
entryPoints.Add(entryPoint);
|
||||
|
||||
// build load + invocation
|
||||
Func<IMarshalContext, ExpressionSyntax> expression = ctx => InvocationExpression
|
||||
(
|
||||
|
@ -170,21 +201,7 @@ namespace Silk.NET.SilkTouch
|
|||
(
|
||||
CastExpression
|
||||
(
|
||||
FunctionPointerType
|
||||
(
|
||||
Identifier(GetCallingConvention(callingConvention)),
|
||||
SeparatedList
|
||||
(
|
||||
ctx.LoadTypes.Select
|
||||
(
|
||||
x => Parameter
|
||||
(
|
||||
Identifier
|
||||
(x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
|
||||
)
|
||||
)
|
||||
)
|
||||
), InvocationExpression
|
||||
fPtrType, InvocationExpression
|
||||
(
|
||||
IdentifierName("Load"), ArgumentList
|
||||
(
|
||||
|
@ -230,8 +247,8 @@ namespace Silk.NET.SilkTouch
|
|||
marshalBuilder.Use(BuildLoadInvoke);
|
||||
|
||||
slotCount++;
|
||||
|
||||
|
||||
|
||||
|
||||
var context = new MarshalContext(compilation, symbol, symbol.GetHashCode() ^ slotCount);
|
||||
|
||||
marshalBuilder.Run(context);
|
||||
|
@ -283,9 +300,7 @@ namespace Silk.NET.SilkTouch
|
|||
catch (Exception ex)
|
||||
{
|
||||
sourceContext.ReportDiagnostic
|
||||
(
|
||||
Diagnostic.Create(Diagnostics.MethodClassFailure, declaration.GetLocation(), ex.ToString())
|
||||
);
|
||||
(Diagnostic.Create(Diagnostics.MethodClassFailure, declaration.GetLocation(), ex.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -346,29 +361,289 @@ namespace Silk.NET.SilkTouch
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
processedSymbols.Add(classSymbol);
|
||||
}
|
||||
|
||||
if (newMembers.Count == 0)
|
||||
return null;
|
||||
|
||||
if (generateVTable && entryPoints.Count > 0)
|
||||
{
|
||||
var vTableMembers = new List<MemberDeclarationSyntax>();
|
||||
|
||||
vTableMembers.Add
|
||||
(
|
||||
FieldDeclaration
|
||||
(
|
||||
List<AttributeListSyntax>(), TokenList(Token(SyntaxKind.PrivateKeyword)),
|
||||
VariableDeclaration
|
||||
(
|
||||
IdentifierName("Silk.NET.Core.Contexts.INativeContext"),
|
||||
SingletonSeparatedList(VariableDeclarator("_ctx"))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
vTableMembers.Add
|
||||
(
|
||||
MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), "Initialize")
|
||||
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword)))
|
||||
.WithParameterList
|
||||
(
|
||||
ParameterList
|
||||
(
|
||||
SeparatedList
|
||||
(
|
||||
new[]
|
||||
{
|
||||
Parameter
|
||||
(Identifier("ctx"))
|
||||
.WithType(IdentifierName("Silk.NET.Core.Contexts.INativeContext")),
|
||||
Parameter
|
||||
(Identifier("maxSlots"))
|
||||
.WithType(PredefinedType(Token(SyntaxKind.IntKeyword)))
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
.WithBody
|
||||
(
|
||||
Block
|
||||
(
|
||||
ExpressionStatement
|
||||
(
|
||||
AssignmentExpression
|
||||
(
|
||||
SyntaxKind.SimpleAssignmentExpression, IdentifierName("_ctx"),
|
||||
IdentifierName("ctx")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
List<VariableDeclaratorSyntax> slotVars = new List<VariableDeclaratorSyntax>();
|
||||
List<StatementSyntax> loadStatements = new List<StatementSyntax>();
|
||||
foreach (var entrypoint in entryPoints)
|
||||
{
|
||||
var name = $"_{entrypoint}";
|
||||
slotVars.Add(VariableDeclarator(name));
|
||||
loadStatements.Add
|
||||
(
|
||||
IfStatement
|
||||
(
|
||||
BinaryExpression
|
||||
(
|
||||
SyntaxKind.EqualsExpression, IdentifierName("entryPoint"),
|
||||
LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(entrypoint))
|
||||
),
|
||||
Block
|
||||
(
|
||||
IfStatement
|
||||
(
|
||||
BinaryExpression
|
||||
(
|
||||
SyntaxKind.NotEqualsExpression, IdentifierName(name),
|
||||
DefaultExpression(IdentifierName("System.IntPtr"))
|
||||
), ReturnStatement(IdentifierName(name))
|
||||
),
|
||||
ExpressionStatement
|
||||
(
|
||||
AssignmentExpression
|
||||
(
|
||||
SyntaxKind.SimpleAssignmentExpression, IdentifierName(name),
|
||||
InvocationExpression
|
||||
(
|
||||
MemberAccessExpression
|
||||
(
|
||||
SyntaxKind.SimpleMemberAccessExpression, IdentifierName("_ctx"),
|
||||
IdentifierName("GetProcAddress")
|
||||
),
|
||||
ArgumentList(SingletonSeparatedList(Argument(IdentifierName("entryPoint"))))
|
||||
)
|
||||
)
|
||||
), ReturnStatement(IdentifierName(name))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
vTableMembers.Add
|
||||
(
|
||||
MethodDeclaration
|
||||
(PredefinedType(Token(SyntaxKind.VoidKeyword)), "GeneratedThrowHelperInvalidSlot")
|
||||
.WithParameterList(ParameterList())
|
||||
.WithModifiers(TokenList(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.StaticKeyword)))
|
||||
.WithBody
|
||||
(
|
||||
Block
|
||||
(
|
||||
ThrowStatement
|
||||
(
|
||||
ObjectCreationExpression
|
||||
(
|
||||
QualifiedName
|
||||
(IdentifierName("System"), IdentifierName("InvalidOperationException"))
|
||||
)
|
||||
.WithArgumentList
|
||||
(
|
||||
ArgumentList
|
||||
(
|
||||
SingletonSeparatedList
|
||||
(
|
||||
Argument
|
||||
(
|
||||
LiteralExpression
|
||||
(
|
||||
SyntaxKind.StringLiteralExpression, Literal("Invalid Slot")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
loadStatements.Add
|
||||
(ExpressionStatement(InvocationExpression(IdentifierName("GeneratedThrowHelperInvalidSlot"))));
|
||||
loadStatements.Add(ReturnStatement(DefaultExpression(IdentifierName("System.IntPtr"))));
|
||||
|
||||
vTableMembers.Add
|
||||
(
|
||||
FieldDeclaration
|
||||
(
|
||||
List<AttributeListSyntax>(), TokenList(Token(SyntaxKind.PrivateKeyword)),
|
||||
VariableDeclaration(IdentifierName("System.IntPtr"), SeparatedList(slotVars))
|
||||
)
|
||||
);
|
||||
|
||||
vTableMembers.Add
|
||||
(
|
||||
MethodDeclaration(IdentifierName("System.IntPtr"), "Load")
|
||||
.WithParameterList
|
||||
(
|
||||
ParameterList
|
||||
(
|
||||
SeparatedList
|
||||
(
|
||||
new[]
|
||||
{
|
||||
Parameter
|
||||
(Identifier("slot"))
|
||||
.WithType(PredefinedType(Token(SyntaxKind.IntKeyword))),
|
||||
Parameter
|
||||
(Identifier("entryPoint"))
|
||||
.WithType(PredefinedType(Token(SyntaxKind.StringKeyword)))
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
.WithBody(Block(loadStatements))
|
||||
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword)))
|
||||
);
|
||||
|
||||
vTableMembers.Add
|
||||
(
|
||||
MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), "Purge")
|
||||
.WithParameterList(ParameterList(SeparatedList<ParameterSyntax>()))
|
||||
.WithBody
|
||||
(
|
||||
Block
|
||||
(
|
||||
entryPoints.Select
|
||||
(
|
||||
x => ExpressionStatement
|
||||
(
|
||||
AssignmentExpression
|
||||
(
|
||||
SyntaxKind.SimpleAssignmentExpression, IdentifierName($"_{x}"),
|
||||
DefaultExpression(IdentifierName("System.IntPtr"))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword)))
|
||||
);
|
||||
|
||||
vTableMembers.Add
|
||||
(
|
||||
MethodDeclaration(PredefinedType(Token(SyntaxKind.VoidKeyword)), "Dispose")
|
||||
.WithParameterList(ParameterList(SeparatedList<ParameterSyntax>()))
|
||||
.WithBody(Block())
|
||||
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword)))
|
||||
);
|
||||
|
||||
newMembers.Add
|
||||
(
|
||||
ClassDeclaration
|
||||
(
|
||||
List<AttributeListSyntax>(),
|
||||
TokenList(Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.SealedKeyword)),
|
||||
Identifier("GeneratedVTable"), null,
|
||||
BaseList
|
||||
(
|
||||
SingletonSeparatedList
|
||||
((BaseTypeSyntax) SimpleBaseType(IdentifierName("Silk.NET.Core.Native.IVTable")))
|
||||
), List<TypeParameterConstraintClauseSyntax>(), List(vTableMembers)
|
||||
)
|
||||
);
|
||||
|
||||
newMembers.Add
|
||||
(
|
||||
MethodDeclaration(IdentifierName("IVTable"), Identifier("CreateVTable"))
|
||||
.WithModifiers(TokenList(Token(SyntaxKind.ProtectedKeyword), Token(SyntaxKind.OverrideKeyword)))
|
||||
.WithExpressionBody
|
||||
(
|
||||
ArrowExpressionClause
|
||||
(
|
||||
ObjectCreationExpression
|
||||
(IdentifierName("GeneratedVTable"))
|
||||
.WithArgumentList(ArgumentList())
|
||||
)
|
||||
)
|
||||
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
|
||||
);
|
||||
}
|
||||
|
||||
var newNamespace = namespaceDeclaration.WithMembers
|
||||
(List(new MemberDeclarationSyntax[] {classDeclaration.WithMembers(List(newMembers)).WithAttributeLists(List<AttributeListSyntax>())})).WithUsings(compilationUnit.Usings);
|
||||
|
||||
(
|
||||
List
|
||||
(
|
||||
new MemberDeclarationSyntax[]
|
||||
{
|
||||
classDeclaration.WithMembers
|
||||
(List(newMembers))
|
||||
.WithAttributeLists(List<AttributeListSyntax>())
|
||||
}
|
||||
)
|
||||
)
|
||||
.WithUsings(compilationUnit.Usings);
|
||||
|
||||
var result = newNamespace.NormalizeWhitespace().ToFullString();
|
||||
stopwatch.Stop();
|
||||
bool reportTelemetry = true;
|
||||
#if !DEBUG
|
||||
reportTelemetry = sourceContext.AnalyzerConfigOptions.GlobalOptions.TryGetValue
|
||||
("silk_touch.telemetry", out var telstr) && bool.Parse(telstr);
|
||||
("silk_touch_telemetry", out var telstr) && bool.Parse(telstr);
|
||||
#endif
|
||||
if (reportTelemetry)
|
||||
sourceContext.ReportDiagnostic(Diagnostic.Create(Diagnostics.BuildInfo, classDeclaration.GetLocation(), slotCount, gcCount, stopwatch.ElapsedMilliseconds + "ms"));
|
||||
sourceContext.ReportDiagnostic
|
||||
(
|
||||
Diagnostic.Create
|
||||
(
|
||||
Diagnostics.BuildInfo, classDeclaration.GetLocation(), slotCount, gcCount,
|
||||
stopwatch.ElapsedMilliseconds + "ms"
|
||||
)
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GetCallingConvention(CallingConvention convention) =>
|
||||
convention switch
|
||||
private static string GetCallingConvention(CallingConvention convention)
|
||||
=> convention switch
|
||||
{
|
||||
// CallingConvention.Winapi => "", netstandard2.0 doesn't allow this
|
||||
CallingConvention.Cdecl => "cdecl",
|
||||
|
@ -381,16 +656,16 @@ namespace Silk.NET.SilkTouch
|
|||
{
|
||||
if (attributeData is null)
|
||||
return null;
|
||||
|
||||
|
||||
var v = new NativeApiAttribute();
|
||||
var dictionary = attributeData.NamedArguments.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
if (dictionary.TryGetValue(nameof(NativeApiAttribute.EntryPoint), out var entryPointConstant))
|
||||
v.EntryPoint = (string) entryPointConstant.Value;
|
||||
|
||||
|
||||
if (dictionary.TryGetValue(nameof(NativeApiAttribute.Prefix), out var prefixConstant))
|
||||
v.Prefix = (string) prefixConstant.Value;
|
||||
|
||||
|
||||
if (dictionary.TryGetValue(nameof(NativeApiAttribute.Convention), out var conventionConstant))
|
||||
v.Convention = (CallingConvention) conventionConstant.Value;
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче