From d94ea64a623cb6453668141cf6cc22c22270f552 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Wed, 1 Nov 2023 00:27:57 +0100 Subject: [PATCH] (#354) CodeGen: implement generic delegate cache --- ...PointerStructMember_arch=Wide.verified.txt | 2 +- .../Contexts/TranslationUnitContext.cs | 36 ++++++++++++++++--- .../Utilities/GenericDelegateTypeCache.cs | 36 +++++++++++++++++++ Cesium.CodeGen/Ir/Types/FunctionType.cs | 23 ++++++++++++ Cesium.CodeGen/Ir/Types/PointerType.cs | 2 +- 5 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 Cesium.CodeGen/Contexts/Utilities/GenericDelegateTypeCache.cs diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerStructMember_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerStructMember_arch=Wide.verified.txt index 1b1914a3..ce0e9141 100644 --- a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerStructMember_arch=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerStructMember_arch=Wide.verified.txt @@ -3,4 +3,4 @@ Type: Foo Fields: - method System.Void *(System.Int32) Foo::x + Cesium.Runtime.FuncPtr`1> Foo::x diff --git a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs index f154004c..993a0f74 100644 --- a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs +++ b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using Cesium.CodeGen.Contexts.Meta; +using Cesium.CodeGen.Contexts.Utilities; using Cesium.CodeGen.Extensions; using Cesium.CodeGen.Ir; using Cesium.CodeGen.Ir.Declarations; @@ -49,6 +50,9 @@ TypeReference GetRuntimeType(string typeName) => _runtimeCPtr = Module.ImportReference(GetRuntimeType(_cPtrFullTypeName)); RuntimeVoidPtr = Module.ImportReference(GetRuntimeType(_voidPtrFullTypeName)); _runtimeFuncPtr = Module.ImportReference(GetRuntimeType(_funcPtrFullTypeName)); + + _importedActionDelegates = new("System", "Action", Module, TypeSystem); + _importedFuncDelegates = new("System", "Func", Module, TypeSystem); } public TypeReference RuntimeCPtr(TypeReference typeReference) @@ -56,13 +60,37 @@ public TypeReference RuntimeCPtr(TypeReference typeReference) return _runtimeCPtr.MakeGenericInstanceType(typeReference); } - public TypeReference RuntimeFuncPtr(TypeReference typeReference) + public TypeReference RuntimeFuncPtr(TypeReference delegateTypeReference) + { + return _runtimeFuncPtr.MakeGenericInstanceType(delegateTypeReference); + } + + /// + /// Resolves a standard delegate type (i.e. an or a ), depending on + /// the return type. + /// + public TypeReference StandardDelegateType(TypeReference returnType, IEnumerable arguments) { - // TODO: Resolve a corresponding delegate type - // return _runtimeFuncPtr.MakeGenericInstanceType(delegateTypeReference); - throw new WipException(WipException.ToDo, "Resolve a delegate for FPtr"); + var isAction = returnType == TypeSystem.Void; + var typeArguments = (isAction ? arguments : arguments.Append(returnType)).ToArray(); + var typeArgumentCount = typeArguments.Length; + if (typeArgumentCount > 16) + { + throw new WipException( + WipException.ToDo, + $"Mapping of function for argument count {typeArgumentCount} is not supported."); + } + + var delegateCache = isAction ? _importedActionDelegates : _importedFuncDelegates; + var delegateType = delegateCache.GetDelegateType(typeArguments.Length); + return typeArguments.Length == 0 + ? delegateType + : delegateType.MakeGenericInstanceType(typeArguments); } + private readonly GenericDelegateTypeCache _importedActionDelegates; + private readonly GenericDelegateTypeCache _importedFuncDelegates; + /// /// Architecturally, there's only one global initializer at the assembly level. But every translation unit may have /// its own set of definitions and thus its own initializer scope built around the same method body. diff --git a/Cesium.CodeGen/Contexts/Utilities/GenericDelegateTypeCache.cs b/Cesium.CodeGen/Contexts/Utilities/GenericDelegateTypeCache.cs new file mode 100644 index 00000000..6d623ac6 --- /dev/null +++ b/Cesium.CodeGen/Contexts/Utilities/GenericDelegateTypeCache.cs @@ -0,0 +1,36 @@ +using System.Globalization; +using Mono.Cecil; + +namespace Cesium.CodeGen.Contexts.Utilities; + +internal record GenericDelegateTypeCache( + string Namespace, + string TypeName, + ModuleDefinition TargetModule, + TypeSystem TypeSystem) +{ + private readonly object _delegateCacheLock = new(); + private readonly Dictionary _cache = new(); + public TypeReference GetDelegateType(int typeArgumentCount) + { + lock (_delegateCacheLock) + { + if (_cache.GetValueOrDefault(typeArgumentCount) is { } result) return result; + return _cache[typeArgumentCount] = FindDelegate(typeArgumentCount); + } + } + + private TypeReference FindDelegate(int typeArgumentCount) + { + var realTypeName = $"{TypeName}`{typeArgumentCount.ToString(CultureInfo.InvariantCulture)}"; + var type = new TypeReference( + Namespace, + realTypeName, + null, + TypeSystem.CoreLibrary); + for (var i = 0; i < typeArgumentCount; ++i) + type.GenericParameters.Add(new GenericParameter(type)); + + return TargetModule.ImportReference(type); + } +} diff --git a/Cesium.CodeGen/Ir/Types/FunctionType.cs b/Cesium.CodeGen/Ir/Types/FunctionType.cs index fb187889..02c2a945 100644 --- a/Cesium.CodeGen/Ir/Types/FunctionType.cs +++ b/Cesium.CodeGen/Ir/Types/FunctionType.cs @@ -9,6 +9,29 @@ internal record FunctionType(ParametersInfo? Parameters, IType ReturnType) : ITy public TypeReference Resolve(TranslationUnitContext context) => throw new AssertException($"Function type {this} cannot be directly expressed in the byte code."); + /// Resolves a delegate type corresponding to this function's signature. + /// + /// Most useful for interop, since every function gets resolved to a or an + /// corresponding to it. + /// + public TypeReference ResolveAsDelegateType(TranslationUnitContext context) + { + var returnType = ReturnType.Resolve(context); + + if (Parameters is null) + throw new CompilationException("Function parameters should not be null."); + + var (parameterInfos, isVoid, isVarArg) = Parameters; + if (isVarArg) + throw new WipException(WipException.ToDo, $"A vararg function is not implemented, yet: {this}."); + + if (parameterInfos.Count == 0 && !isVoid) + throw new WipException(WipException.ToDo, $"A function with an empty parameter list is not implemented, yet: {this}."); + + var arguments = parameterInfos.Select(p => p.Type.Resolve(context)); + return context.StandardDelegateType(returnType, arguments); + } + public TypeReference ResolvePointer(TranslationUnitContext context) { var pointer = new FunctionPointerType diff --git a/Cesium.CodeGen/Ir/Types/PointerType.cs b/Cesium.CodeGen/Ir/Types/PointerType.cs index 737e4066..6dccb08d 100644 --- a/Cesium.CodeGen/Ir/Types/PointerType.cs +++ b/Cesium.CodeGen/Ir/Types/PointerType.cs @@ -30,7 +30,7 @@ public TypeReference ResolveForTypeMember(TranslationUnitContext context) => { TargetArchitectureSet.Wide => Base switch { - FunctionType => context.RuntimeFuncPtr(Base.ResolveForTypeMember(context)), + FunctionType rawFunc => context.RuntimeFuncPtr(rawFunc.ResolveAsDelegateType(context)), PrimitiveType { Kind: PrimitiveTypeKind.Void } => context.RuntimeVoidPtr, _ => context.RuntimeCPtr(Base.ResolveForTypeMember(context)), },