Skip to content

Commit

Permalink
(#354) CodeGen: implement generic delegate cache
Browse files Browse the repository at this point in the history
  • Loading branch information
ForNeVeR committed Oct 31, 2023
1 parent 3f530d6 commit d94ea64
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

Type: Foo
Fields:
method System.Void *(System.Int32) Foo::x
Cesium.Runtime.FuncPtr`1<System.Action`1<System.Int32>> Foo::x
36 changes: 32 additions & 4 deletions Cesium.CodeGen/Contexts/TranslationUnitContext.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -49,20 +50,47 @@ 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)
{
return _runtimeCPtr.MakeGenericInstanceType(typeReference);
}

public TypeReference RuntimeFuncPtr(TypeReference typeReference)
public TypeReference RuntimeFuncPtr(TypeReference delegateTypeReference)
{
return _runtimeFuncPtr.MakeGenericInstanceType(delegateTypeReference);
}

/// <summary>
/// Resolves a standard delegate type (i.e. an <see cref="Action"/> or a <see cref="Func{TResult}"/>), depending on
/// the return type.
/// </summary>
public TypeReference StandardDelegateType(TypeReference returnType, IEnumerable<TypeReference> 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;

/// <remarks>
/// 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.
Expand Down
36 changes: 36 additions & 0 deletions Cesium.CodeGen/Contexts/Utilities/GenericDelegateTypeCache.cs
Original file line number Diff line number Diff line change
@@ -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<int, TypeReference> _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);
}
}
23 changes: 23 additions & 0 deletions Cesium.CodeGen/Ir/Types/FunctionType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");

/// <summary>Resolves a delegate type corresponding to this function's signature.</summary>
/// <remarks>
/// Most useful for interop, since every function gets resolved to a <see cref="Func{TResult}"/> or an
/// <see cref="Action{T}"/> corresponding to it.
/// </remarks>
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
Expand Down
2 changes: 1 addition & 1 deletion Cesium.CodeGen/Ir/Types/PointerType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
},
Expand Down

0 comments on commit d94ea64

Please sign in to comment.