Skip to content

Commit

Permalink
(#354) CodeGen: call implicit cast operator for VoidPtr
Browse files Browse the repository at this point in the history
  • Loading branch information
ForNeVeR committed Nov 5, 2023
1 parent 2e93313 commit cf9ce69
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 78 deletions.
2 changes: 1 addition & 1 deletion Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public Task VoidPtrInterop(TargetArchitectureSet architecture) => DoTest(
@"using Cesium.Runtime;
public static class Test
{
public static int Func(VoidPtr<int> ptr) => 1;
public static int Func(VoidPtr ptr) => 1;
}
", """
__cli_import("Test::Func")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Module: Primary
Type: <Module>
Methods:
System.Int32 <Module>::main()
Locals:
System.Int32 V_0
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: ldloca.s V_0
IL_0004: call Cesium.Runtime.VoidPtr Cesium.Runtime.VoidPtr::op_Implicit(System.Void*)
IL_0009: call System.Int32 Test::Func(Cesium.Runtime.VoidPtr)
IL_000e: ret

System.Int32 <Module>::<SyntheticEntrypoint>()
Locals:
System.Int32 V_0
IL_0000: call System.Int32 <Module>::main()
IL_0005: stloc.s V_0
IL_0007: ldloc.s V_0
IL_0009: call System.Void Cesium.Runtime.RuntimeHelpers::Exit(System.Int32)
IL_000e: ldloc.s V_0
IL_0010: ret
62 changes: 62 additions & 0 deletions Cesium.CodeGen/Contexts/AssemblyContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
using System.Text;
using Cesium.Ast;
using Cesium.CodeGen.Contexts.Meta;
using Cesium.CodeGen.Contexts.Utilities;
using Cesium.CodeGen.Extensions;
using Cesium.CodeGen.Ir.Emitting;
using Cesium.CodeGen.Ir.Lowering;
using Cesium.CodeGen.Ir.Types;
using Cesium.Core;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;

namespace Cesium.CodeGen.Contexts;

Expand Down Expand Up @@ -74,6 +76,13 @@ public AssemblyDefinition VerifyAndGetAssembly()
private readonly Lazy<TypeDefinition> _constantPool;
private MethodDefinition? _globalInitializer;

private readonly TypeReference _runtimeCPtr;
public TypeReference RuntimeVoidPtr { get; }
private readonly TypeReference _runtimeFuncPtr;

private readonly Lazy<MethodReference> _voidPtrConverter;
public MethodReference VoidPtrConverter => _voidPtrConverter.Value;

private AssemblyContext(
AssemblyDefinition assembly,
ModuleDefinition module,
Expand Down Expand Up @@ -115,6 +124,59 @@ private AssemblyContext(
{
GlobalType = Module.GetType("<Module>");
}

TypeDefinition GetRuntimeType(string typeName) =>
CesiumRuntimeAssembly.GetType(typeName) ??
throw new AssertException($"Could not find type {typeName} in the runtime assembly.");

_runtimeCPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.CPtrFullTypeName));
RuntimeVoidPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.VoidPtrFullTypeName));
_runtimeFuncPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.FuncPtrFullTypeName));

_importedActionDelegates = new("System", "Action", Module);
_importedFuncDelegates = new("System", "Func", Module);

_voidPtrConverter = new(() =>
{
var voidPtrType = GetRuntimeType(TypeSystemEx.VoidPtrFullTypeName);
return Module.ImportReference(voidPtrType.Methods.Single(m => m.Name == "op_Implicit"));
});
}

public TypeReference RuntimeCPtr(TypeReference typeReference)
{
return _runtimeCPtr.MakeGenericInstanceType(typeReference);
}

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

private readonly GenericDelegateTypeCache _importedActionDelegates;
private readonly GenericDelegateTypeCache _importedFuncDelegates;

/// <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)
{
var isAction = returnType == Module.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);
}

internal VariableInfo? GetGlobalField(string identifier)
Expand Down
4 changes: 2 additions & 2 deletions Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ internal record FunctionInfo(
ParametersInfo? Parameters,
IType ReturnType,
StorageClass StorageClass,
bool IsDefined)
bool IsDefined,
MethodReference? MethodReference = null)
{
public MethodReference? MethodReference { get; set; }
public string? CliImportMember { get; set; }

public void VerifySignatureEquality(string name, ParametersInfo? parameters, IType returnType)
Expand Down
105 changes: 50 additions & 55 deletions Cesium.CodeGen/Contexts/TranslationUnitContext.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
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;
using Cesium.CodeGen.Ir.Types;
using Cesium.Core;
using Mono.Cecil;
using Mono.Cecil.Rocks;
using PointerType = Cesium.CodeGen.Ir.Types.PointerType;

namespace Cesium.CodeGen.Contexts;
Expand All @@ -30,63 +28,12 @@ public class TranslationUnitContext

private GlobalConstructorScope? _initializerScope;

private readonly TypeReference _runtimeCPtr;
public TypeReference RuntimeVoidPtr { get; }
private readonly TypeReference _runtimeFuncPtr;

public TranslationUnitContext(AssemblyContext assemblyContext, string name)
{
AssemblyContext = assemblyContext;
Name = name;

TypeReference GetRuntimeType(string typeName) =>
assemblyContext.CesiumRuntimeAssembly.GetType(typeName) ??
throw new AssertException($"Could not find type {typeName} in the runtime assembly.");

_runtimeCPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.CPtrFullTypeName));
RuntimeVoidPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.VoidPtrFullTypeName));
_runtimeFuncPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.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 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)
{
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 All @@ -102,12 +49,12 @@ internal void DeclareFunction(string identifier, FunctionInfo functionInfo)
var existingDeclaration = Functions.GetValueOrDefault(identifier);
if (existingDeclaration is null)
{
Functions.Add(identifier, functionInfo);
if (functionInfo.CliImportMember is not null)
{
var method = this.MethodLookup(functionInfo.CliImportMember, functionInfo.Parameters!, functionInfo.ReturnType);
functionInfo.MethodReference = method;
functionInfo = ProcessCliImport(functionInfo, method);
}
Functions.Add(identifier, functionInfo);
}
else
{
Expand Down Expand Up @@ -273,4 +220,52 @@ private TypeDefinition CreateTranslationUnitLevelType()
Module.Types.Add(type);
return type;
}

private FunctionInfo ProcessCliImport(FunctionInfo declaration, MethodReference implementation)
{
return declaration with
{
MethodReference = implementation,
Parameters = declaration.Parameters is null ? null : declaration.Parameters with
{
Parameters = ProcessParameters(declaration.Parameters.Parameters)
}
};

List<ParameterInfo> ProcessParameters(ICollection<ParameterInfo> parameters)
{
// For now, only wrap the interop types.
if (implementation.Parameters.Count != parameters.Count)
throw new CompilationException(
$"Parameter count for function {declaration.CliImportMember} " +
$"doesn't match the parameter count of imported CLI method {implementation.FullName}.");

return parameters.Zip(implementation.Parameters)
.Select(pair =>
{
var (declared, actual) = pair;
var type = WrapInteropType(actual.ParameterType);
if (type == null) return declared;
return declared with { Type = type };
}).ToList();
}

InteropType? WrapInteropType(TypeReference actual)
{
if (actual.FullName == TypeSystemEx.VoidPtrFullTypeName)
return new InteropType(actual);

if (actual.IsGenericInstance)
{
var parent = actual.GetElementType();
if (parent.FullName == TypeSystemEx.CPtrFullTypeName
|| parent.FullName == TypeSystemEx.FuncPtrFullTypeName)
{
return new InteropType(actual);
}
}

return null;
}
}
}
5 changes: 2 additions & 3 deletions Cesium.CodeGen/Contexts/Utilities/GenericDelegateTypeCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ namespace Cesium.CodeGen.Contexts.Utilities;
internal record GenericDelegateTypeCache(
string Namespace,
string TypeName,
ModuleDefinition TargetModule,
TypeSystem TypeSystem)
ModuleDefinition TargetModule)
{
private readonly object _delegateCacheLock = new();
private readonly Dictionary<int, TypeReference> _cache = new();
Expand All @@ -27,7 +26,7 @@ private TypeReference FindDelegate(int typeArgumentCount)
Namespace,
realTypeName,
null,
TypeSystem.CoreLibrary);
TargetModule.TypeSystem.CoreLibrary);
for (var i = 0; i < typeArgumentCount; ++i)
type.GenericParameters.Add(new GenericParameter(type));

Expand Down
14 changes: 10 additions & 4 deletions Cesium.CodeGen/Extensions/TypeSystemEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Cesium.CodeGen.Ir.Types;
using Cesium.Core;
using Mono.Cecil;
using PointerType = Mono.Cecil.PointerType;

namespace Cesium.CodeGen.Extensions;

Expand Down Expand Up @@ -89,7 +90,7 @@ private static bool Match(
}

var declReturnReified = returnType.Resolve(context);
if (!TypesCorrespond(declReturnReified, method.ReturnType))
if (!TypesCorrespond(context.TypeSystem, declReturnReified, method.ReturnType))
{
similarMethods.Add((method, $"Returns types do not match: {declReturnReified.Name} in declaration, {method.ReturnType.Name} in source."));
return false;
Expand All @@ -103,7 +104,7 @@ private static bool Match(
var srcParam = methodParameters[i];
var srcParamType = srcParam.ParameterType;

if (!TypesCorrespond(declParamType, srcParamType))
if (!TypesCorrespond(context.TypeSystem, declParamType, srcParamType))
{
similarMethods.Add((method, $"Type of argument #{i} does not match: {declParamType} in declaration, {srcParamType} in source."));
return false;
Expand Down Expand Up @@ -141,7 +142,7 @@ private static bool Match(
/// This tries to handle the pointer interop between the arch-independent pointer types introduced by the Cesium
/// compatibility model and the actual runtime pointer types.
/// </remarks>
private static bool TypesCorrespond(TypeReference type1, TypeReference type2)
private static bool TypesCorrespond(TypeSystem typeSystem, TypeReference type1, TypeReference type2)
{
// let type 1 to be pointer out of these two
if (type2.IsPointer || type2.IsFunctionPointer) (type1, type2) = (type2, type1);
Expand All @@ -154,12 +155,17 @@ private static bool TypesCorrespond(TypeReference type1, TypeReference type2)
return type1.FullName == type2.FullName;
}

if (type2.FullName.Equals(VoidPtrFullTypeName))
{
return type1 is PointerType pt && pt.ElementType.IsEqualTo(typeSystem.Void);
}

if (!type2.IsGenericInstance) return false;
type2 = type2.GetElementType();
if (type1.IsPointer)
{
// TODO: Analyze the generic argument.
return type2.FullName == CPtrFullTypeName || type2.FullName == VoidPtrFullTypeName;
return type2.FullName == CPtrFullTypeName;
}

if (type1.IsFunctionPointer)
Expand Down
10 changes: 1 addition & 9 deletions Cesium.CodeGen/Ir/Expressions/FunctionCallExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,6 @@ public override IExpression Lower(IDeclarationScope scope)
throw new CompilationException($"Function \"{functionName}\" was not found.");
}

if (callee.MethodReference is not null)
throw new WipException(WipException.ToDo,
"TODO: Inspect the actual method reference argument list instead of the formal argument list");
return new FunctionCallExpression(
_function,
callee,
Expand All @@ -91,12 +88,7 @@ public override IExpression Lower(IDeclarationScope scope)

private List<IExpression> ConvertArgs(IDeclarationScope scope, ParametersInfo? parameters)
{
int firstVarArgArgument = 0;
if (parameters?.IsVarArg == true)
{
firstVarArgArgument = parameters.Parameters.Count;
}

int firstVarArgArgument = parameters?.Parameters.Count ?? 0;
return _arguments.Select((a, index) =>
{
IType targetType;
Expand Down
9 changes: 9 additions & 0 deletions Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ public void EmitTo(IEmitScope scope)
Add(OpCodes.Conv_R8);
else if (TargetType is PointerType || TargetType.Equals(ts.NativeInt) || TargetType.Equals(ts.NativeUInt))
Add(OpCodes.Conv_I);
else if (TargetType is InteropType iType)
{
if (iType.UnderlyingType.FullName == TypeSystemEx.VoidPtrFullTypeName)
{
scope.Method.Body.Instructions.Add(
Instruction.Create(OpCodes.Call, iType.GetConvertCall(scope.AssemblyContext)));
}
else throw new WipException(WipException.ToDo, $"Cast to {iType.UnderlyingType} is not implemented, yet.");
}
else
throw new AssertException($"Type {TargetType} is not supported.");

Expand Down
Loading

0 comments on commit cf9ce69

Please sign in to comment.