* Allow custom VTable implementations

* Optionally generate VTable

* Enable VTable generation globally

* Distinct Entrypoints

* Add Swap overload
This commit is contained in:
HurricanKai 2020-08-30 16:15:18 +02:00 коммит произвёл GitHub
Родитель 0fe87ad2d5
Коммит 1781f16d78
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 327 добавлений и 58 удалений

Просмотреть файл

@ -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;