From f556c450331f0fe7a9da1e0be5d88f75107b5e16 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 11 Dec 2022 18:58:01 +0100 Subject: [PATCH 01/53] (#354) CodeGen: implement the wide architecture set --- .../ArchitectureDependentCodeTests.cs | 17 +++++++ .../ArchitectureDependentTypeTests.cs | 1 + ...rArrayMemberAssign_arch=Bit32.verified.txt | 44 ++++++++++++++++++ ...rArrayMemberAssign_arch=Bit64.verified.txt | 44 ++++++++++++++++++ ...rrayMemberAssign_arch=Dynamic.verified.txt | 46 +++++++++++++++++++ ...erArrayMemberAssign_arch=Wide.verified.txt | 44 ++++++++++++++++++ ...deTests.StaticArray_arch=Wide.verified.txt | 35 ++++++++++++++ ...deTests.StructArray_arch=Wide.verified.txt | 19 ++++++++ ...s.StructWithPointer_arch=Wide.verified.txt | 19 ++++++++ Cesium.CodeGen/Ir/Types/PointerType.cs | 5 +- Cesium.CodeGen/Ir/Types/StructType.cs | 1 + Cesium.CodeGen/TargetArchitectureSet.cs | 5 +- README.md | 7 +-- docs/architecture-sets.md | 2 +- 14 files changed, 282 insertions(+), 7 deletions(-) create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Bit32.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Bit64.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Dynamic.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Wide.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StaticArray_arch=Wide.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructArray_arch=Wide.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt diff --git a/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs b/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs index 2a908d0a..d644568e 100644 --- a/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs +++ b/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs @@ -16,6 +16,7 @@ private static Task DoTest(TargetArchitectureSet arch, string source) [InlineData(TargetArchitectureSet.Bit64)] [InlineData(TargetArchitectureSet.Bit32)] [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Wide)] public Task StaticArray(TargetArchitectureSet arch) => DoTest(arch, """ int main(void) { @@ -29,6 +30,7 @@ int main(void) [InlineData(TargetArchitectureSet.Bit64)] [InlineData(TargetArchitectureSet.Bit32)] [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Wide)] public Task StructArray(TargetArchitectureSet arch) => DoTest(arch, """ typedef struct { char *ptr; } foo; @@ -43,6 +45,7 @@ int main(void) // TODO[#355]: [InlineData(TargetArchitectureSet.Bit64)] // TODO[#355]: [InlineData(TargetArchitectureSet.Bit32)] [InlineData(TargetArchitectureSet.Dynamic)] + // TODO[#355] [InlineData(TargetArchitectureSet.Wide)] public Task TwoMemberStructArray(TargetArchitectureSet arch) => DoTest(arch, """ typedef struct { char *ptr; int len; } foo; @@ -51,5 +54,19 @@ int main(void) foo x[3]; return 0; } +"""); + + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Bit64)] + [InlineData(TargetArchitectureSet.Bit32)] + [InlineData(TargetArchitectureSet.Wide)] + public Task PointerArrayMemberAssign(TargetArchitectureSet arch) => DoTest(arch, """ +int main(void) +{ + void *x[3]; + x[2] = 0; + x[0] = x[2]; +} """); } diff --git a/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs b/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs index 40d33f5b..93325b2f 100644 --- a/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs +++ b/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs @@ -19,6 +19,7 @@ private static Task DoTest(TargetArchitectureSet arch, string source, string @na [Theory] [InlineData(TargetArchitectureSet.Bit64)] [InlineData(TargetArchitectureSet.Bit32)] + [InlineData(TargetArchitectureSet.Wide)] public Task StructWithPointer(TargetArchitectureSet arch) => DoTest(arch, """ typedef struct { diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Bit32.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Bit32.verified.txt new file mode 100644 index 00000000..5bef4bde --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Bit32.verified.txt @@ -0,0 +1,44 @@ +System.Int32 ::main() + Locals: + System.Void* V_0 + IL_0000: ldc.i4.s 12 + IL_0002: conv.u + IL_0003: localloc + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: conv.u + IL_0008: ldc.i4.4 + IL_0009: ldc.i4.2 + IL_000a: mul + IL_000b: conv.i + IL_000c: add + IL_000d: ldc.i4.0 + IL_000e: stind.i + IL_000f: ldloc.0 + IL_0010: conv.u + IL_0011: ldc.i4.4 + IL_0012: ldc.i4.0 + IL_0013: mul + IL_0014: conv.i + IL_0015: add + IL_0016: ldloc.0 + IL_0017: conv.u + IL_0018: ldc.i4.4 + IL_0019: ldc.i4.2 + IL_001a: mul + IL_001b: conv.i + IL_001c: add + IL_001d: ldind.i + IL_001e: stind.i + IL_001f: ldc.i4.0 + IL_0020: ret + +System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Bit64.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Bit64.verified.txt new file mode 100644 index 00000000..b53d65b3 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Bit64.verified.txt @@ -0,0 +1,44 @@ +System.Int32 ::main() + Locals: + System.Void* V_0 + IL_0000: ldc.i4.s 24 + IL_0002: conv.u + IL_0003: localloc + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: conv.u + IL_0008: ldc.i4.8 + IL_0009: ldc.i4.2 + IL_000a: mul + IL_000b: conv.i + IL_000c: add + IL_000d: ldc.i4.0 + IL_000e: stind.i + IL_000f: ldloc.0 + IL_0010: conv.u + IL_0011: ldc.i4.8 + IL_0012: ldc.i4.0 + IL_0013: mul + IL_0014: conv.i + IL_0015: add + IL_0016: ldloc.0 + IL_0017: conv.u + IL_0018: ldc.i4.8 + IL_0019: ldc.i4.2 + IL_001a: mul + IL_001b: conv.i + IL_001c: add + IL_001d: ldind.i + IL_001e: stind.i + IL_001f: ldc.i4.0 + IL_0020: ret + +System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Dynamic.verified.txt new file mode 100644 index 00000000..ef516a63 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Dynamic.verified.txt @@ -0,0 +1,46 @@ +System.Int32 ::main() + Locals: + System.Void* V_0 + IL_0000: sizeof System.Void* + IL_0006: ldc.i4.3 + IL_0007: mul + IL_0008: conv.u + IL_0009: localloc + IL_000b: stloc.0 + IL_000c: ldloc.0 + IL_000d: conv.u + IL_000e: sizeof System.Void* + IL_0014: ldc.i4.2 + IL_0015: mul + IL_0016: conv.i + IL_0017: add + IL_0018: ldc.i4.0 + IL_0019: stind.i + IL_001a: ldloc.0 + IL_001b: conv.u + IL_001c: sizeof System.Void* + IL_0022: ldc.i4.0 + IL_0023: mul + IL_0024: conv.i + IL_0025: add + IL_0026: ldloc.0 + IL_0027: conv.u + IL_0028: sizeof System.Void* + IL_002e: ldc.i4.2 + IL_002f: mul + IL_0030: conv.i + IL_0031: add + IL_0032: ldind.i + IL_0033: stind.i + IL_0034: ldc.i4.0 + IL_0035: ret + +System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Wide.verified.txt new file mode 100644 index 00000000..b53d65b3 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Wide.verified.txt @@ -0,0 +1,44 @@ +System.Int32 ::main() + Locals: + System.Void* V_0 + IL_0000: ldc.i4.s 24 + IL_0002: conv.u + IL_0003: localloc + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: conv.u + IL_0008: ldc.i4.8 + IL_0009: ldc.i4.2 + IL_000a: mul + IL_000b: conv.i + IL_000c: add + IL_000d: ldc.i4.0 + IL_000e: stind.i + IL_000f: ldloc.0 + IL_0010: conv.u + IL_0011: ldc.i4.8 + IL_0012: ldc.i4.0 + IL_0013: mul + IL_0014: conv.i + IL_0015: add + IL_0016: ldloc.0 + IL_0017: conv.u + IL_0018: ldc.i4.8 + IL_0019: ldc.i4.2 + IL_001a: mul + IL_001b: conv.i + IL_001c: add + IL_001d: ldind.i + IL_001e: stind.i + IL_001f: ldc.i4.0 + IL_0020: ret + +System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StaticArray_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StaticArray_arch=Wide.verified.txt new file mode 100644 index 00000000..4e864d80 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StaticArray_arch=Wide.verified.txt @@ -0,0 +1,35 @@ +System.Int32 ::main() + Locals: + System.Int32* V_0 + IL_0000: ldc.i4 2400 + IL_0005: conv.u + IL_0006: localloc + IL_0008: stloc.0 + IL_0009: ldloc.0 + IL_000a: conv.u + IL_000b: ldc.i4.8 + IL_000c: ldc.i4 299 + IL_0011: mul + IL_0012: conv.i + IL_0013: add + IL_0014: ldc.i4.0 + IL_0015: stind.i + IL_0016: ldloc.0 + IL_0017: conv.u + IL_0018: ldc.i4.8 + IL_0019: ldc.i4 299 + IL_001e: mul + IL_001f: conv.i + IL_0020: add + IL_0021: ldind.i + IL_0022: ret + +System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructArray_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructArray_arch=Wide.verified.txt new file mode 100644 index 00000000..31df5fc5 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructArray_arch=Wide.verified.txt @@ -0,0 +1,19 @@ +System.Int32 ::main() + Locals: + foo* V_0 + IL_0000: ldc.i4.s 24 + IL_0002: conv.u + IL_0003: localloc + IL_0005: stloc.0 + IL_0006: ldc.i4.0 + IL_0007: ret + +System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt new file mode 100644 index 00000000..c53b7f26 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt @@ -0,0 +1,19 @@ +Module: Primary + Type: + + Type: foo + Nested types: + Type: foo/x + Pack: 0 + Size: 8 + Custom attributes: + - CompilerGeneratedAttribute() + - UnsafeValueTypeAttribute() + + Fields: + System.Byte* foo/x::FixedElementField + Fields: + foo/x foo::x + Custom attributes: + - FixedBufferAttribute(System.Byte*, 8) + diff --git a/Cesium.CodeGen/Ir/Types/PointerType.cs b/Cesium.CodeGen/Ir/Types/PointerType.cs index 3ad5b8ee..e12757e4 100644 --- a/Cesium.CodeGen/Ir/Types/PointerType.cs +++ b/Cesium.CodeGen/Ir/Types/PointerType.cs @@ -6,9 +6,9 @@ namespace Cesium.CodeGen.Ir.Types; -internal record PointerType(IType Base) : IType +internal sealed record PointerType(IType Base) : IType { - public virtual TypeReference Resolve(TranslationUnitContext context) + public TypeReference Resolve(TranslationUnitContext context) { if (Base is FunctionType ft) return ft.ResolvePointer(context); @@ -22,6 +22,7 @@ public virtual TypeReference Resolve(TranslationUnitContext context) TargetArchitectureSet.Dynamic => null, TargetArchitectureSet.Bit32 => 4, TargetArchitectureSet.Bit64 => 8, + TargetArchitectureSet.Wide => 8, _ => throw new AssertException($"Unknown architecture set: {arch}.") }; diff --git a/Cesium.CodeGen/Ir/Types/StructType.cs b/Cesium.CodeGen/Ir/Types/StructType.cs index 3c488184..7852cf8b 100644 --- a/Cesium.CodeGen/Ir/Types/StructType.cs +++ b/Cesium.CodeGen/Ir/Types/StructType.cs @@ -33,6 +33,7 @@ public TypeDefinition Emit(string name, TranslationUnitContext context) // TODO[#355]: enable explicit layout. break; case TargetArchitectureSet.Bit64: + case TargetArchitectureSet.Wide: structType.PackingSize = 8; // TODO[#355]: enable explicit layout. break; diff --git a/Cesium.CodeGen/TargetArchitectureSet.cs b/Cesium.CodeGen/TargetArchitectureSet.cs index a99550ae..be391b99 100644 --- a/Cesium.CodeGen/TargetArchitectureSet.cs +++ b/Cesium.CodeGen/TargetArchitectureSet.cs @@ -16,5 +16,8 @@ public enum TargetArchitectureSet Bit32, /// An architecture with 64-bit pointers. Targets ARM64 and x86-64 CPUs. - Bit64 + Bit64, + + /// An architecture with 64-bit pointers (even on 32-bit runtime). Targets any CPUs. + Wide } diff --git a/README.md b/README.md index 417461e9..f1798938 100644 --- a/README.md +++ b/README.md @@ -66,9 +66,10 @@ $ mono ./out.exe # run with Mono - `NetStandard` for .NET Standard - `Net` for .NET 5+ - `--arch `: specifies the [target architecture set][docs.architecture-sets], defaults to `Dynamic`. Possible values are: - - `Dynamic` (machine-independent), - - `Bit32` (for 32-bit CPUs), - - `Bit64` (for 64-bit CPUs). + - `Dynamic` (machine-independent, calculates pointer size and structure layout in runtime), + - `Bit32` (for 32-bit architectures), + - `Bit64` (for 64-bit architectures), + - `Wide` (machine-independent, uses 64-bit pointers even on 32-bit architectures). - `--modulekind `: specifies the output module kind; by default, it is autodetected from the output file extension - `Dll`: gets detected from a `.dll` extension - `Console`: gets detected from an `.exe` extension diff --git a/docs/architecture-sets.md b/docs/architecture-sets.md index a13746e4..9bf89f6f 100644 --- a/docs/architecture-sets.md +++ b/docs/architecture-sets.md @@ -25,6 +25,6 @@ Cesium aims to support the following architecture sets: Not every C construct allows to use dynamically-calculated size (in particular, it's impossible for pointer-dependent arrays embedded into structures), so this architecture doesn't support all the C standard. It still should be practical for many applications. **This architecture is machine-independent** and results in producing of an Any CPU-targeting assembly. -- **Wide** architecture **is not implemented, yet** (see issue #354), and uses the fixed pointer size of 64 bits on all computers. This allows it to cover all the features of the C standard, for the cost of some redundancy on 32-bit architectures, and slightly different method signatures for .NET interop. +- **Wide** architecture uses the fixed pointer size of 64 bits on all computers. This allows it to cover all the features of the C standard, for the cost of some redundancy on 32-bit architectures, and slightly different method signatures for .NET interop. **This architecture is machine-independent** and results in producing of an Any CPU-targeting assembly. From 2c8853a3809fd47689a2005134148ce0f70eb473 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 11 Dec 2022 22:11:59 +0100 Subject: [PATCH 02/53] (#354) Runtime: introduce the CPtr type --- Cesium.Runtime/CPtr.cs | 35 ++++++++++++++++++++++ Cesium.Runtime/RuntimeHelpers.cs | 25 ++++++++++++++++ Cesium.Runtime/StdIoFunctions.cs | 50 +++++++------------------------ Cesium.Runtime/StdLibFunctions.cs | 8 ++--- Cesium.Runtime/StringFunctions.cs | 17 ++++++----- docs/architecture-sets.md | 2 ++ docs/type-system.md | 6 +++- 7 files changed, 90 insertions(+), 53 deletions(-) create mode 100644 Cesium.Runtime/CPtr.cs diff --git a/Cesium.Runtime/CPtr.cs b/Cesium.Runtime/CPtr.cs new file mode 100644 index 00000000..d974bacc --- /dev/null +++ b/Cesium.Runtime/CPtr.cs @@ -0,0 +1,35 @@ +namespace Cesium.Runtime; + +/// A class encapsulating an opaque pointer (aka void* in C). +public unsafe readonly struct CPtr +{ + private readonly long _value; + + private CPtr(long value) + { + _value = value; + } + + public static implicit operator CPtr(void* ptr) => new((long)ptr); + public void* AsPtr() => (void*)_value; + public TResult* AsPtr() where TResult : unmanaged => (TResult*)_value; + public IntPtr AsIntPtr() => (IntPtr)AsPtr(); +} + + +/// A class encapsulating an object pointer. +/// Type this pointer may be resolved to. +public unsafe readonly struct CPtr where T : unmanaged +{ + private readonly long _value; + + private CPtr(long value) + { + _value = value; + } + + public static implicit operator CPtr(T* ptr) => new((long)ptr); + public T* AsPtr() => (T*)_value; + public TResult* AsPtr() where TResult : unmanaged => (TResult*)_value; + public IntPtr AsIntPtr() => (IntPtr)AsPtr(); +} diff --git a/Cesium.Runtime/RuntimeHelpers.cs b/Cesium.Runtime/RuntimeHelpers.cs index d11b113d..898b08a2 100644 --- a/Cesium.Runtime/RuntimeHelpers.cs +++ b/Cesium.Runtime/RuntimeHelpers.cs @@ -63,4 +63,29 @@ public static void FreeGlobalField(void* field) { Marshal.FreeHGlobal((IntPtr)field); } + + internal static string? Unmarshal(byte* str) + { +#if NETSTANDARD + Encoding encoding = Encoding.UTF8; + int byteLength = 0; + byte* search = str; + while (*search != '\0') + { + byteLength++; + search++; + } + + int stringLength = encoding.GetCharCount(str, byteLength); + string s = new string('\0', stringLength); + fixed (char* pTempChars = s) + { + encoding.GetChars(str, byteLength, pTempChars, stringLength); + } + + return s; +#else + return Marshal.PtrToStringUTF8((nint)str); +#endif + } } diff --git a/Cesium.Runtime/StdIoFunctions.cs b/Cesium.Runtime/StdIoFunctions.cs index 6867789a..50ca3088 100644 --- a/Cesium.Runtime/StdIoFunctions.cs +++ b/Cesium.Runtime/StdIoFunctions.cs @@ -1,8 +1,3 @@ -using System.Runtime.InteropServices; -#if NETSTANDARD -using System.Text; -#endif - namespace Cesium.Runtime; /// @@ -10,11 +5,11 @@ namespace Cesium.Runtime; /// public unsafe static class StdIoFunctions { - public static void PutS(byte* str) + public static void PutS(CPtr str) { try { - Console.Write(Unmarshal(str)); + Console.Write(RuntimeHelpers.Unmarshal(str.AsPtr())); // return 0; // TODO[#156]: Uncomment } catch (Exception) // TODO[#154]: Exception handling. @@ -24,9 +19,9 @@ public static void PutS(byte* str) } } - public static int PrintF(byte* str, void* varargs) + public static int PrintF(CPtr str, CPtr varargs) { - var formatString = Unmarshal(str); + var formatString = RuntimeHelpers.Unmarshal(str.AsPtr()); if (formatString == null) { return -1; @@ -52,20 +47,20 @@ public static int PrintF(byte* str, void* varargs) switch (formatSpecifier) { case "s": - string? stringValue = Unmarshal((byte*)((long*)varargs)[consumedArgs]); + string? stringValue = RuntimeHelpers.Unmarshal((byte*)varargs.AsPtr()[consumedArgs]); Console.Write(stringValue); consumedBytes += stringValue?.Length ?? 0; consumedArgs++; break; case "c": - Console.Write((char)(byte)((long*)varargs)[consumedArgs]); + Console.Write((char)(byte)varargs.AsPtr()[consumedArgs]); consumedBytes++; consumedArgs++; break; case "d": case "li": case "i": - int intValue = (int)((long*)varargs)[consumedArgs]; + int intValue = (int)varargs.AsPtr()[consumedArgs]; var intValueString = intValue.ToString(); Console.Write(intValueString); consumedBytes += intValueString.Length; @@ -73,21 +68,21 @@ public static int PrintF(byte* str, void* varargs) break; case "u": case "lu": - uint uintValue = (uint)((long*)varargs)[consumedArgs]; + uint uintValue = (uint)varargs.AsPtr()[consumedArgs]; var uintValueString = uintValue.ToString(); Console.Write(uintValueString); consumedBytes += uintValueString.Length; consumedArgs++; break; case "f": - var floatNumber = ((double*)varargs)[consumedArgs]; + var floatNumber = varargs.AsPtr()[consumedArgs]; string floatNumberString = floatNumber.ToString("F6"); Console.Write(floatNumberString); consumedBytes += floatNumberString.Length; consumedArgs++; break; case "p": - nint pointerValue = ((nint*)varargs)[consumedArgs]; + nint pointerValue = varargs.AsPtr()[consumedArgs]; string pointerValueString = pointerValue.ToString("X"); Console.Write(pointerValueString); consumedBytes += pointerValueString.Length; @@ -105,29 +100,4 @@ public static int PrintF(byte* str, void* varargs) Console.Write(remainderString); return consumedBytes + remainderString.Length; } - - internal static string? Unmarshal(byte* str) - { -#if NETSTANDARD - Encoding encoding = Encoding.UTF8; - int byteLength = 0; - byte* search = str; - while (*search != '\0') - { - byteLength++; - search++; - } - - int stringLength = encoding.GetCharCount(str, byteLength); - string s = new string('\0', stringLength); - fixed (char* pTempChars = s) - { - encoding.GetChars(str, byteLength, pTempChars, stringLength); - } - - return s; -#else - return Marshal.PtrToStringUTF8((nint)str); -#endif - } } diff --git a/Cesium.Runtime/StdLibFunctions.cs b/Cesium.Runtime/StdLibFunctions.cs index 307a8ca8..094f7f1d 100644 --- a/Cesium.Runtime/StdLibFunctions.cs +++ b/Cesium.Runtime/StdLibFunctions.cs @@ -1,5 +1,3 @@ -using System.Runtime.InteropServices; - namespace Cesium.Runtime; /// @@ -8,7 +6,7 @@ namespace Cesium.Runtime; public unsafe static class StdLibFunctions { public const int RAND_MAX = 0x7FFFFFFF; - private static System.Random shared = new(); + private static Random shared = new(); public static int Abs(int value) { @@ -31,9 +29,9 @@ public static void SRand(uint seed) shared = new Random((int)seed); } - public static int System(byte* command) + public static int System(CPtr command) { - switch (StdIoFunctions.Unmarshal(command)) + switch (RuntimeHelpers.Unmarshal(command.AsPtr())) { case "cls": case "clear": diff --git a/Cesium.Runtime/StringFunctions.cs b/Cesium.Runtime/StringFunctions.cs index 904aa104..1ece4c3d 100644 --- a/Cesium.Runtime/StringFunctions.cs +++ b/Cesium.Runtime/StringFunctions.cs @@ -1,30 +1,33 @@ -namespace Cesium.Runtime; - +#if NETSTANDARD using System.Text; +#endif +#if NET6_0 using System.Runtime.InteropServices; -using System.Diagnostics; +#endif + +namespace Cesium.Runtime; /// /// Functions declared in the string.h /// public unsafe static class StringFunctions { - public static uint StrLen(byte* str) + public static uint StrLen(CPtr str) { #if NETSTANDARD Encoding encoding = Encoding.UTF8; int byteLength = 0; - byte* search = str; + byte* search = str.AsPtr(); while (*search != '\0') { byteLength++; search++; } - int stringLength = encoding.GetCharCount(str, byteLength); + int stringLength = encoding.GetCharCount(str.AsPtr(), byteLength); return (uint)stringLength; #else - return (uint)(Marshal.PtrToStringUTF8((nint)str)?.Length ?? 0); + return (uint)(Marshal.PtrToStringUTF8(str.AsIntPtr())?.Length ?? 0); #endif } } diff --git a/docs/architecture-sets.md b/docs/architecture-sets.md index 9bf89f6f..9911ea1b 100644 --- a/docs/architecture-sets.md +++ b/docs/architecture-sets.md @@ -28,3 +28,5 @@ Cesium aims to support the following architecture sets: - **Wide** architecture uses the fixed pointer size of 64 bits on all computers. This allows it to cover all the features of the C standard, for the cost of some redundancy on 32-bit architectures, and slightly different method signatures for .NET interop. **This architecture is machine-independent** and results in producing of an Any CPU-targeting assembly. + +Specifically for the **wide** architecture, the type `CPtr` was introduced. It corresponds to a 64-bit pointer universally, and the **wide** architecture uses it in place of normal pointer types everywhere in the API. For cross-compatibility with any architecture, this type is also used in the Cesium.Runtime library. diff --git a/docs/type-system.md b/docs/type-system.md index 26aac569..6aae2163 100644 --- a/docs/type-system.md +++ b/docs/type-system.md @@ -27,4 +27,8 @@ This should be kept in sync with section **6.7.2 Type Specifiers** of the actual | `double _Complex` | N/A | | `long double _Complex` | N/A | -All pointer types are mapped to the CLI pointers of the corresponding type. +All the pointer types are mapped to the CLI pointers of the corresponding type on **dynamic**, **32b** and **64b** architecture sets. + +For the **wide** architecture set, the pointers are mapped to `Cesium.Runtime.CPtr` (for `void*`) and `Cesium.Runtime.CPtr` (for typed pointers). This architecture requires 64-bit pointer types everywhere, and thus can't use the CLI pointer types. + +To be compatible with both **wide** and other architecture sets, the Cesium.Runtime library uses `CPtr` in all of its standard APIs. From 15ed22785cf5f5e26f2108abefbea5ffc66536fb Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Tue, 13 Dec 2022 23:23:32 +0100 Subject: [PATCH 03/53] (#354) FunctionType: add a TODO about the wide architecture --- Cesium.CodeGen/Ir/Types/FunctionType.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cesium.CodeGen/Ir/Types/FunctionType.cs b/Cesium.CodeGen/Ir/Types/FunctionType.cs index fb187889..6869289a 100644 --- a/Cesium.CodeGen/Ir/Types/FunctionType.cs +++ b/Cesium.CodeGen/Ir/Types/FunctionType.cs @@ -11,6 +11,9 @@ public TypeReference Resolve(TranslationUnitContext context) => public TypeReference ResolvePointer(TranslationUnitContext context) { + if (context.AssemblyContext.ArchitectureSet == TargetArchitectureSet.Wide) + throw new WipException(WipException.ToDo, $"Cannot resolve function pointer {this} in wide architecture."); + var pointer = new FunctionPointerType { ReturnType = ReturnType.Resolve(context) From 244b6bdfb5eb8c6ceb180db0b7af7f0e84a4f4dc Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 12 Feb 2023 16:40:46 +0100 Subject: [PATCH 04/53] (#354) Docs: document the current decision on pointer portability --- docs/type-system.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/type-system.md b/docs/type-system.md index 6aae2163..db0cc4e8 100644 --- a/docs/type-system.md +++ b/docs/type-system.md @@ -29,6 +29,14 @@ This should be kept in sync with section **6.7.2 Type Specifiers** of the actual All the pointer types are mapped to the CLI pointers of the corresponding type on **dynamic**, **32b** and **64b** architecture sets. -For the **wide** architecture set, the pointers are mapped to `Cesium.Runtime.CPtr` (for `void*`) and `Cesium.Runtime.CPtr` (for typed pointers). This architecture requires 64-bit pointer types everywhere, and thus can't use the CLI pointer types. +The **wide** architecture set supports mapping to raw pointers as well, but also supports special mapping types that have architecture-independent size and memory alignment, according to the following table. -To be compatible with both **wide** and other architecture sets, the Cesium.Runtime library uses `CPtr` in all of its standard APIs. +| C type | CLI Type | +|--------------------------------------------------------|--------------------------| +| `void*` | `Cesium.Runtime.CPtr` | +| Function pointer | `Cesium.Runtime.FPtr` | +| `T*` (where `T` is not `void` and not a function type) | `Cesium.Runtime.CPtr` | + +Note that function and function pointer signatures still use raw pointers even in the **wide** architecture set, because this has no effect on memory requirement and alignment, and thus type safety is preferred by default. + +To be compatible with both **wide** and other architecture sets, the Cesium.Runtime library uses `CPtr` and `FPtr`, where appropriate, in all of its standard APIs. From 8ed11c6d48f6b4a4349c12186b5c9e38cd578844 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 12 Feb 2023 20:20:13 +0100 Subject: [PATCH 05/53] (#354) CPtr, FPtr: add tests --- Cesium.Runtime.Tests/PtrTests.cs | 29 +++++++++++++++++++++++++++++ Cesium.Runtime/CPtr.cs | 4 ++-- Cesium.Runtime/FPtr.cs | 14 ++++++++++++++ Cesium.Runtime/StringFunctions.cs | 2 +- 4 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 Cesium.Runtime.Tests/PtrTests.cs create mode 100644 Cesium.Runtime/FPtr.cs diff --git a/Cesium.Runtime.Tests/PtrTests.cs b/Cesium.Runtime.Tests/PtrTests.cs new file mode 100644 index 00000000..ba8120f7 --- /dev/null +++ b/Cesium.Runtime.Tests/PtrTests.cs @@ -0,0 +1,29 @@ +namespace Cesium.Runtime.Tests; + +public unsafe class PtrTests +{ + [Fact] + public void CPtrTests() + { + CPtr v = (void*)0x1234; + Assert.Equal(0x1234L, (long)v.AsPtr()); + Assert.Equal(0x1234L, (long)v.AsPtr()); + + Assert.Equal(sizeof(long), sizeof(CPtr)); + + CPtr t = (int*)0x2345; + Assert.Equal(0x2345L, (long)t.AsPtr()); + Assert.Equal((IntPtr)0x2345, t.AsIntPtr()); + Assert.Equal(0x2345L, (long)t.AsPtr()); + + Assert.Equal(sizeof(long), sizeof(CPtr)); + } + + [Fact] + public void FPtrTests() + { + var a = new FPtr((void*)0x1234); + Assert.Equal(0x1234L, (long)a.AsPtr()); + Assert.Equal(sizeof(long), sizeof(FPtr)); + } +} diff --git a/Cesium.Runtime/CPtr.cs b/Cesium.Runtime/CPtr.cs index d974bacc..0fb097d5 100644 --- a/Cesium.Runtime/CPtr.cs +++ b/Cesium.Runtime/CPtr.cs @@ -1,7 +1,7 @@ namespace Cesium.Runtime; /// A class encapsulating an opaque pointer (aka void* in C). -public unsafe readonly struct CPtr +public readonly unsafe struct CPtr { private readonly long _value; @@ -19,7 +19,7 @@ private CPtr(long value) /// A class encapsulating an object pointer. /// Type this pointer may be resolved to. -public unsafe readonly struct CPtr where T : unmanaged +public readonly unsafe struct CPtr where T : unmanaged { private readonly long _value; diff --git a/Cesium.Runtime/FPtr.cs b/Cesium.Runtime/FPtr.cs new file mode 100644 index 00000000..0b609c53 --- /dev/null +++ b/Cesium.Runtime/FPtr.cs @@ -0,0 +1,14 @@ +namespace Cesium.Runtime; + +/// A class encapsulating a C function pointer. +public readonly unsafe struct FPtr where TDelegate : Delegate // TODO: Think about vararg and empty parameter list encoding. +{ + private readonly long _value; + + public FPtr(void* ptr) + { + _value = (long)ptr; + } + + public void* AsPtr() => (void*)_value; +} diff --git a/Cesium.Runtime/StringFunctions.cs b/Cesium.Runtime/StringFunctions.cs index 1ece4c3d..cce1e32d 100644 --- a/Cesium.Runtime/StringFunctions.cs +++ b/Cesium.Runtime/StringFunctions.cs @@ -10,7 +10,7 @@ namespace Cesium.Runtime; /// /// Functions declared in the string.h /// -public unsafe static class StringFunctions +public static unsafe class StringFunctions { public static uint StrLen(CPtr str) { From 7c732515242eef12e1c89f95cd0983c819738e16 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Fri, 17 Feb 2023 22:52:01 +0100 Subject: [PATCH 06/53] (#354) VoidPtr, FuncPtr --- Cesium.Runtime.Tests/PtrTests.cs | 16 ++++++++++------ Cesium.Runtime/{FPtr.cs => FuncPtr.cs} | 4 ++-- Cesium.Runtime/StdIoFunctions.cs | 2 +- Cesium.Runtime/{CPtr.cs => VoidPtr.cs} | 6 +++--- docs/type-system.md | 4 ++-- 5 files changed, 18 insertions(+), 14 deletions(-) rename Cesium.Runtime/{FPtr.cs => FuncPtr.cs} (56%) rename Cesium.Runtime/{CPtr.cs => VoidPtr.cs} (86%) diff --git a/Cesium.Runtime.Tests/PtrTests.cs b/Cesium.Runtime.Tests/PtrTests.cs index ba8120f7..afae7533 100644 --- a/Cesium.Runtime.Tests/PtrTests.cs +++ b/Cesium.Runtime.Tests/PtrTests.cs @@ -3,14 +3,18 @@ namespace Cesium.Runtime.Tests; public unsafe class PtrTests { [Fact] - public void CPtrTests() + public void VoidPtrTests() { - CPtr v = (void*)0x1234; + VoidPtr v = (void*)0x1234; Assert.Equal(0x1234L, (long)v.AsPtr()); Assert.Equal(0x1234L, (long)v.AsPtr()); - Assert.Equal(sizeof(long), sizeof(CPtr)); + Assert.Equal(sizeof(long), sizeof(VoidPtr)); + } + [Fact] + public void CPtrTests() + { CPtr t = (int*)0x2345; Assert.Equal(0x2345L, (long)t.AsPtr()); Assert.Equal((IntPtr)0x2345, t.AsIntPtr()); @@ -20,10 +24,10 @@ public void CPtrTests() } [Fact] - public void FPtrTests() + public void FuncPtrTests() { - var a = new FPtr((void*)0x1234); + var a = new FuncPtr((void*)0x1234); Assert.Equal(0x1234L, (long)a.AsPtr()); - Assert.Equal(sizeof(long), sizeof(FPtr)); + Assert.Equal(sizeof(long), sizeof(FuncPtr)); } } diff --git a/Cesium.Runtime/FPtr.cs b/Cesium.Runtime/FuncPtr.cs similarity index 56% rename from Cesium.Runtime/FPtr.cs rename to Cesium.Runtime/FuncPtr.cs index 0b609c53..478136b4 100644 --- a/Cesium.Runtime/FPtr.cs +++ b/Cesium.Runtime/FuncPtr.cs @@ -1,11 +1,11 @@ namespace Cesium.Runtime; /// A class encapsulating a C function pointer. -public readonly unsafe struct FPtr where TDelegate : Delegate // TODO: Think about vararg and empty parameter list encoding. +public readonly unsafe struct FuncPtr where TDelegate : Delegate // TODO: Think about vararg and empty parameter list encoding. { private readonly long _value; - public FPtr(void* ptr) + public FuncPtr(void* ptr) { _value = (long)ptr; } diff --git a/Cesium.Runtime/StdIoFunctions.cs b/Cesium.Runtime/StdIoFunctions.cs index 50ca3088..a2e63109 100644 --- a/Cesium.Runtime/StdIoFunctions.cs +++ b/Cesium.Runtime/StdIoFunctions.cs @@ -19,7 +19,7 @@ public static void PutS(CPtr str) } } - public static int PrintF(CPtr str, CPtr varargs) + public static int PrintF(CPtr str, VoidPtr varargs) { var formatString = RuntimeHelpers.Unmarshal(str.AsPtr()); if (formatString == null) diff --git a/Cesium.Runtime/CPtr.cs b/Cesium.Runtime/VoidPtr.cs similarity index 86% rename from Cesium.Runtime/CPtr.cs rename to Cesium.Runtime/VoidPtr.cs index 0fb097d5..ec581f48 100644 --- a/Cesium.Runtime/CPtr.cs +++ b/Cesium.Runtime/VoidPtr.cs @@ -1,16 +1,16 @@ namespace Cesium.Runtime; /// A class encapsulating an opaque pointer (aka void* in C). -public readonly unsafe struct CPtr +public readonly unsafe struct VoidPtr { private readonly long _value; - private CPtr(long value) + private VoidPtr(long value) { _value = value; } - public static implicit operator CPtr(void* ptr) => new((long)ptr); + public static implicit operator VoidPtr(void* ptr) => new((long)ptr); public void* AsPtr() => (void*)_value; public TResult* AsPtr() where TResult : unmanaged => (TResult*)_value; public IntPtr AsIntPtr() => (IntPtr)AsPtr(); diff --git a/docs/type-system.md b/docs/type-system.md index db0cc4e8..e8f9287b 100644 --- a/docs/type-system.md +++ b/docs/type-system.md @@ -33,8 +33,8 @@ The **wide** architecture set supports mapping to raw pointers as well, but also | C type | CLI Type | |--------------------------------------------------------|--------------------------| -| `void*` | `Cesium.Runtime.CPtr` | -| Function pointer | `Cesium.Runtime.FPtr` | +| `void*` | `Cesium.Runtime.VoidPtr` | +| Function pointer | `Cesium.Runtime.FuncPtr` | | `T*` (where `T` is not `void` and not a function type) | `Cesium.Runtime.CPtr` | Note that function and function pointer signatures still use raw pointers even in the **wide** architecture set, because this has no effect on memory requirement and alignment, and thus type safety is preferred by default. From 3833c05beff3266d376673898fdbe8282306626c Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Fri, 17 Feb 2023 23:29:32 +0100 Subject: [PATCH 07/53] (#354) Add more tests --- .../ArchitectureDependentCodeTests.cs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs b/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs index d644568e..18a92ee9 100644 --- a/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs +++ b/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs @@ -8,8 +8,7 @@ public class ArchitectureDependentCodeTests : CodeGenTestBase private static Task DoTest(TargetArchitectureSet arch, string source) { var assembly = GenerateAssembly(runtime: default, arch: arch, sources: source); - var moduleType = assembly.Modules.Single().GetType(""); - return VerifyMethods(moduleType, arch); + return VerifyTypes(assembly, arch); } [Theory] @@ -68,5 +67,45 @@ int main(void) x[2] = 0; x[0] = x[2]; } +"""); + + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Bit64)] + [InlineData(TargetArchitectureSet.Bit32)] + [InlineData(TargetArchitectureSet.Wide)] + public Task PointerFunctionSignature(TargetArchitectureSet arch) => DoTest(arch, """ +int foo(void *x) +{ + return 0; +} +"""); + + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Bit64)] + [InlineData(TargetArchitectureSet.Bit32)] + [InlineData(TargetArchitectureSet.Wide)] + public Task FunctionPointerParameter(TargetArchitectureSet arch) => DoTest(arch, """ +typedef void (*func)(int, int); +typedef void (*v_func)(void); + +int foo(func x) { return 0; } +int v_foo(v_func x) { return 0; } +"""); + // TODO: empty-paren-func ptr + // TODO: vararg-func ptr + + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Bit64)] + [InlineData(TargetArchitectureSet.Bit32)] + [InlineData(TargetArchitectureSet.Wide)] + public Task FunctionPointerStructMember(TargetArchitectureSet arch) => DoTest(arch, """ +typedef void (*func)(int); +struct Foo +{ + func x; +}; """); } From bfbbec1f8119babbe433671818ec304003e263b8 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Tue, 21 Feb 2023 02:00:03 +0100 Subject: [PATCH 08/53] (#354) Functions: add TODO --- Cesium.CodeGen/Ir/BlockItems/FunctionDeclaration.cs | 2 +- Cesium.CodeGen/Ir/BlockItems/FunctionDefinition.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cesium.CodeGen/Ir/BlockItems/FunctionDeclaration.cs b/Cesium.CodeGen/Ir/BlockItems/FunctionDeclaration.cs index 76bc4f2f..fb8b9e98 100644 --- a/Cesium.CodeGen/Ir/BlockItems/FunctionDeclaration.cs +++ b/Cesium.CodeGen/Ir/BlockItems/FunctionDeclaration.cs @@ -21,7 +21,7 @@ public FunctionDeclaration(string identifier, FunctionType functionType, string? public IBlockItem Lower(IDeclarationScope scope) { - return this; + return this; // TODO: scope.ResolveType(_functionType) } public void EmitTo(IEmitScope scope) diff --git a/Cesium.CodeGen/Ir/BlockItems/FunctionDefinition.cs b/Cesium.CodeGen/Ir/BlockItems/FunctionDefinition.cs index a05fadc6..a21d1874 100644 --- a/Cesium.CodeGen/Ir/BlockItems/FunctionDefinition.cs +++ b/Cesium.CodeGen/Ir/BlockItems/FunctionDefinition.cs @@ -41,7 +41,7 @@ public FunctionDefinition(Ast.FunctionDefinition function) public IBlockItem Lower(IDeclarationScope scope) { - return this; + return this; // TODO: scope.ResolveType(_functionType) } public void EmitTo(IEmitScope scope) From 6f39aa4604f50e4db15f02e2a4f07cd814bdc86b Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sat, 29 Jul 2023 23:01:50 +0200 Subject: [PATCH 09/53] (#356) Docs: document the new pointer types in the architecture set documetation --- docs/architecture-sets.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/architecture-sets.md b/docs/architecture-sets.md index 9911ea1b..5a290003 100644 --- a/docs/architecture-sets.md +++ b/docs/architecture-sets.md @@ -29,4 +29,6 @@ Cesium aims to support the following architecture sets: **This architecture is machine-independent** and results in producing of an Any CPU-targeting assembly. -Specifically for the **wide** architecture, the type `CPtr` was introduced. It corresponds to a 64-bit pointer universally, and the **wide** architecture uses it in place of normal pointer types everywhere in the API. For cross-compatibility with any architecture, this type is also used in the Cesium.Runtime library. +Specifically for the **wide** architecture, the types `VoidPtr`, `CPtr` and `FuncPtr` were introduced. They correspond to 64-bit pointers universally, and the **wide** architecture uses it in place of normal pointer types everywhere in the API. For cross-compatibility with any architecture, these types are also used in the Cesium.Runtime library. See [the type system documentation][docs.type-system] for more information. + +[docs.type-system]: type-system.md From 7cf15f03b54fdda0eb93fd7a46eb7d84ef259877 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sat, 29 Jul 2023 23:04:34 +0200 Subject: [PATCH 10/53] (#384) Docs: more clarification --- docs/type-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/type-system.md b/docs/type-system.md index e8f9287b..218b3b53 100644 --- a/docs/type-system.md +++ b/docs/type-system.md @@ -37,6 +37,6 @@ The **wide** architecture set supports mapping to raw pointers as well, but also | Function pointer | `Cesium.Runtime.FuncPtr` | | `T*` (where `T` is not `void` and not a function type) | `Cesium.Runtime.CPtr` | -Note that function and function pointer signatures still use raw pointers even in the **wide** architecture set, because this has no effect on memory requirement and alignment, and thus type safety is preferred by default. +Note that function and function pointer signatures (i.e. the arguments and the return types) still use raw pointers even in the **wide** architecture set, because this has no effect on memory requirement and alignment, and thus type safety is preferred by default. To be compatible with both **wide** and other architecture sets, the Cesium.Runtime library uses `CPtr` and `FPtr`, where appropriate, in all of its standard APIs. From b5512a091ac121181c1d0034c211aa38fca18591 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sat, 29 Jul 2023 23:14:37 +0200 Subject: [PATCH 11/53] (#354) Docs: more clarifications --- docs/architecture-sets.md | 2 +- docs/type-system.md | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/architecture-sets.md b/docs/architecture-sets.md index 5a290003..85a3a406 100644 --- a/docs/architecture-sets.md +++ b/docs/architecture-sets.md @@ -29,6 +29,6 @@ Cesium aims to support the following architecture sets: **This architecture is machine-independent** and results in producing of an Any CPU-targeting assembly. -Specifically for the **wide** architecture, the types `VoidPtr`, `CPtr` and `FuncPtr` were introduced. They correspond to 64-bit pointers universally, and the **wide** architecture uses it in place of normal pointer types everywhere in the API. For cross-compatibility with any architecture, these types are also used in the Cesium.Runtime library. See [the type system documentation][docs.type-system] for more information. +Specifically for the **wide** architecture, the types `VoidPtr`, `CPtr` and `FuncPtr` were introduced. They correspond to 64-bit pointers universally, and the **wide** architecture uses it in place of normal pointer types everywhere in the API. For cross-compatibility with any architecture, these types are also used in the Cesium.Runtime library. See [the type system documentation][docs.type-system] for more information. [docs.type-system]: type-system.md diff --git a/docs/type-system.md b/docs/type-system.md index 218b3b53..a0d05e36 100644 --- a/docs/type-system.md +++ b/docs/type-system.md @@ -31,12 +31,12 @@ All the pointer types are mapped to the CLI pointers of the corresponding type o The **wide** architecture set supports mapping to raw pointers as well, but also supports special mapping types that have architecture-independent size and memory alignment, according to the following table. -| C type | CLI Type | -|--------------------------------------------------------|--------------------------| -| `void*` | `Cesium.Runtime.VoidPtr` | -| Function pointer | `Cesium.Runtime.FuncPtr` | -| `T*` (where `T` is not `void` and not a function type) | `Cesium.Runtime.CPtr` | +| C type | CLI Type | +|--------------------------------------------------------|-------------------------------------| +| `void*` | `Cesium.Runtime.VoidPtr` | +| Function pointer | `Cesium.Runtime.FuncPtr` | +| `T*` (where `T` is not `void` and not a function type) | `Cesium.Runtime.CPtr` | -Note that function and function pointer signatures (i.e. the arguments and the return types) still use raw pointers even in the **wide** architecture set, because this has no effect on memory requirement and alignment, and thus type safety is preferred by default. +Note that function and function pointer signatures (i.e. the arguments and the return types) still use raw pointers even in the **wide** architecture set, because this has no effect on memory requirement and alignment, and thus more type safety is preferred by default. -To be compatible with both **wide** and other architecture sets, the Cesium.Runtime library uses `CPtr` and `FPtr`, where appropriate, in all of its standard APIs. +To be compatible with both **wide** and other architecture sets, the Cesium.Runtime library uses `VoidPtr`, `CPtr` and `FuncPtr`, where appropriate, in all of its standard APIs. From ec67da2dcdb47fb8cd7355018d6b5548c9a90f73 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sat, 5 Aug 2023 16:51:40 +0200 Subject: [PATCH 12/53] (#354) Roll back the runtime lib changes --- Cesium.Runtime/StdIoFunctions.cs | 10 ++++------ Cesium.Runtime/StdLibFunctions.cs | 4 ++-- Cesium.Runtime/StringFunctions.cs | 8 ++++---- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Cesium.Runtime/StdIoFunctions.cs b/Cesium.Runtime/StdIoFunctions.cs index 5b15dc89..bed592d3 100644 --- a/Cesium.Runtime/StdIoFunctions.cs +++ b/Cesium.Runtime/StdIoFunctions.cs @@ -1,5 +1,3 @@ -using System.IO; -using System.Runtime.InteropServices; #if NETSTANDARD using System.Text; #endif @@ -39,11 +37,11 @@ static StdIoFunctions() }); } - public static int PutS(CPtr str) + public static int PutS(byte* str) { try { - Console.WriteLine(RuntimeHelpers.Unmarshal(str.AsPtr())); + Console.WriteLine(RuntimeHelpers.Unmarshal(str)); return 0; } catch (Exception) // TODO[#154]: Exception handling. @@ -86,9 +84,9 @@ public static int PutC(byte character, void* stream) } } - public static int PrintF(CPtr str, VoidPtr varargs) + public static int PrintF(byte* str, VoidPtr varargs) { - var formatString = RuntimeHelpers.Unmarshal(str.AsPtr()); + var formatString = RuntimeHelpers.Unmarshal(str); if (formatString == null) { return -1; diff --git a/Cesium.Runtime/StdLibFunctions.cs b/Cesium.Runtime/StdLibFunctions.cs index c4880872..1739ef92 100644 --- a/Cesium.Runtime/StdLibFunctions.cs +++ b/Cesium.Runtime/StdLibFunctions.cs @@ -32,9 +32,9 @@ public static void SRand(uint seed) shared = new Random((int)seed); } - public static int System(CPtr command) + public static int System(byte* command) { - string? shellCommand = RuntimeHelpers.Unmarshal(command.AsPtr()); + string? shellCommand = RuntimeHelpers.Unmarshal(command); if (shellCommand is null) { return 8 /*ENOEXEC*/; diff --git a/Cesium.Runtime/StringFunctions.cs b/Cesium.Runtime/StringFunctions.cs index 1735697c..63fffc9f 100644 --- a/Cesium.Runtime/StringFunctions.cs +++ b/Cesium.Runtime/StringFunctions.cs @@ -11,17 +11,17 @@ namespace Cesium.Runtime; /// public static unsafe class StringFunctions { - public static nuint StrLen(CPtr str) + public static nuint StrLen(byte* str) { #if NETSTANDARD - if (str.AsPtr() == null) + if (str == null) { return 0; } Encoding encoding = Encoding.UTF8; int byteLength = 0; - byte* search = str.AsPtr(); + byte* search = str; while (*search != '\0') { byteLength++; @@ -31,7 +31,7 @@ public static nuint StrLen(CPtr str) int stringLength = encoding.GetCharCount(str.AsPtr(), byteLength); return (uint)stringLength; #else - return (uint)(Marshal.PtrToStringUTF8(str.AsIntPtr())?.Length ?? 0); + return (uint)(Marshal.PtrToStringUTF8((IntPtr)str)?.Length ?? 0); #endif } public static byte* StrCpy(byte* dest, byte* src) From 704d062139c714edddd4d05c9b4ea16b4f3e8a67 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 13 Aug 2023 00:21:24 +0200 Subject: [PATCH 13/53] (#354) Runtime: roll back the unnecessary changes --- Cesium.Runtime/StdIoFunctions.cs | 15 +++++++-------- Cesium.Runtime/StringFunctions.cs | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Cesium.Runtime/StdIoFunctions.cs b/Cesium.Runtime/StdIoFunctions.cs index bed592d3..d9e87e51 100644 --- a/Cesium.Runtime/StdIoFunctions.cs +++ b/Cesium.Runtime/StdIoFunctions.cs @@ -1,5 +1,4 @@ #if NETSTANDARD -using System.Text; #endif namespace Cesium.Runtime; @@ -84,7 +83,7 @@ public static int PutC(byte character, void* stream) } } - public static int PrintF(byte* str, VoidPtr varargs) + public static int PrintF(byte* str, void* varargs) { var formatString = RuntimeHelpers.Unmarshal(str); if (formatString == null) @@ -112,20 +111,20 @@ public static int PrintF(byte* str, VoidPtr varargs) switch (formatSpecifier) { case "s": - string? stringValue = RuntimeHelpers.Unmarshal((byte*)varargs.AsPtr()[consumedArgs]); + string? stringValue = RuntimeHelpers.Unmarshal((byte*)((long*)varargs)[consumedArgs]); Console.Write(stringValue); consumedBytes += stringValue?.Length ?? 0; consumedArgs++; break; case "c": - Console.Write((char)(byte)varargs.AsPtr()[consumedArgs]); + Console.Write((char)(byte)((long*)varargs)[consumedArgs]); consumedBytes++; consumedArgs++; break; case "d": case "li": case "i": - int intValue = (int)varargs.AsPtr()[consumedArgs]; + int intValue = (int)((long*)varargs)[consumedArgs]; var intValueString = intValue.ToString(); Console.Write(intValueString); consumedBytes += intValueString.Length; @@ -133,21 +132,21 @@ public static int PrintF(byte* str, VoidPtr varargs) break; case "u": case "lu": - uint uintValue = (uint)varargs.AsPtr()[consumedArgs]; + uint uintValue = (uint)((long*)varargs)[consumedArgs]; var uintValueString = uintValue.ToString(); Console.Write(uintValueString); consumedBytes += uintValueString.Length; consumedArgs++; break; case "f": - var floatNumber = varargs.AsPtr()[consumedArgs]; + var floatNumber = ((double*)varargs)[consumedArgs]; string floatNumberString = floatNumber.ToString("F6"); Console.Write(floatNumberString); consumedBytes += floatNumberString.Length; consumedArgs++; break; case "p": - nint pointerValue = varargs.AsPtr()[consumedArgs]; + nint pointerValue = ((nint*)varargs)[consumedArgs]; string pointerValueString = pointerValue.ToString("X"); Console.Write(pointerValueString); consumedBytes += pointerValueString.Length; diff --git a/Cesium.Runtime/StringFunctions.cs b/Cesium.Runtime/StringFunctions.cs index 63fffc9f..f6764313 100644 --- a/Cesium.Runtime/StringFunctions.cs +++ b/Cesium.Runtime/StringFunctions.cs @@ -28,10 +28,10 @@ public static nuint StrLen(byte* str) search++; } - int stringLength = encoding.GetCharCount(str.AsPtr(), byteLength); + int stringLength = encoding.GetCharCount(str, byteLength); return (uint)stringLength; #else - return (uint)(Marshal.PtrToStringUTF8((IntPtr)str)?.Length ?? 0); + return (uint)(Marshal.PtrToStringUTF8((nint)str)?.Length ?? 0); #endif } public static byte* StrCpy(byte* dest, byte* src) From 1fefa3aab1cd6d5cafadd86f7e04ffb739726a67 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 13 Aug 2023 22:03:10 +0200 Subject: [PATCH 14/53] (#354) Apply test data diff --- ...onPointerParameter_arch=Bit32.verified.txt | 10 +++ ...onPointerParameter_arch=Bit64.verified.txt | 10 +++ ...PointerParameter_arch=Dynamic.verified.txt | 10 +++ ...erArrayMemberAssign_arch=Wide.verified.txt | 83 +++++++++---------- ...erFunctionSignature_arch=Wide.verified.txt | 6 ++ ...deTests.StaticArray_arch=Wide.verified.txt | 67 ++++++++------- ...deTests.StructArray_arch=Wide.verified.txt | 43 ++++++---- ...s.StructWithPointer_arch=Wide.verified.txt | 8 +- 8 files changed, 138 insertions(+), 99 deletions(-) create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Bit32.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Bit64.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Dynamic.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerFunctionSignature_arch=Wide.verified.txt diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Bit32.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Bit32.verified.txt new file mode 100644 index 00000000..d6cb9eeb --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Bit32.verified.txt @@ -0,0 +1,10 @@ +Module: Primary + Type: + Methods: + System.Int32 ::foo(method System.Void *(System.Int32,System.Int32) x) + IL_0000: ldc.i4.0 + IL_0001: ret + + System.Int32 ::v_foo(method System.Void *() x) + IL_0000: ldc.i4.0 + IL_0001: ret diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Bit64.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Bit64.verified.txt new file mode 100644 index 00000000..d6cb9eeb --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Bit64.verified.txt @@ -0,0 +1,10 @@ +Module: Primary + Type: + Methods: + System.Int32 ::foo(method System.Void *(System.Int32,System.Int32) x) + IL_0000: ldc.i4.0 + IL_0001: ret + + System.Int32 ::v_foo(method System.Void *() x) + IL_0000: ldc.i4.0 + IL_0001: ret diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Dynamic.verified.txt new file mode 100644 index 00000000..d6cb9eeb --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Dynamic.verified.txt @@ -0,0 +1,10 @@ +Module: Primary + Type: + Methods: + System.Int32 ::foo(method System.Void *(System.Int32,System.Int32) x) + IL_0000: ldc.i4.0 + IL_0001: ret + + System.Int32 ::v_foo(method System.Void *() x) + IL_0000: ldc.i4.0 + IL_0001: ret diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Wide.verified.txt index b53d65b3..7facb640 100644 --- a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerArrayMemberAssign_arch=Wide.verified.txt @@ -1,44 +1,41 @@ -System.Int32 ::main() - Locals: - System.Void* V_0 - IL_0000: ldc.i4.s 24 - IL_0002: conv.u - IL_0003: localloc - IL_0005: stloc.0 - IL_0006: ldloc.0 - IL_0007: conv.u - IL_0008: ldc.i4.8 - IL_0009: ldc.i4.2 - IL_000a: mul - IL_000b: conv.i - IL_000c: add - IL_000d: ldc.i4.0 - IL_000e: stind.i - IL_000f: ldloc.0 - IL_0010: conv.u - IL_0011: ldc.i4.8 - IL_0012: ldc.i4.0 - IL_0013: mul - IL_0014: conv.i - IL_0015: add - IL_0016: ldloc.0 - IL_0017: conv.u - IL_0018: ldc.i4.8 - IL_0019: ldc.i4.2 - IL_001a: mul - IL_001b: conv.i - IL_001c: add - IL_001d: ldind.i - IL_001e: stind.i - IL_001f: ldc.i4.0 - IL_0020: ret +Module: Primary + Type: + Methods: + System.Int32 ::main() + Locals: + System.Void* V_0 + IL_0000: ldc.i4.s 24 + IL_0002: conv.u + IL_0003: localloc + IL_0005: stloc.0 + IL_0006: ldloc.0 + IL_0007: ldc.i4.8 + IL_0008: ldc.i4.2 + IL_0009: mul + IL_000a: add + IL_000b: ldc.i4.0 + IL_000c: stind.i + IL_000d: ldloc.0 + IL_000e: ldc.i4.8 + IL_000f: ldc.i4.0 + IL_0010: mul + IL_0011: add + IL_0012: ldloc.0 + IL_0013: ldc.i4.8 + IL_0014: ldc.i4.2 + IL_0015: mul + IL_0016: add + IL_0017: ldind.i + IL_0018: stind.i + IL_0019: ldc.i4.0 + IL_001a: ret -System.Int32 ::() - Locals: - System.Int32 V_0 - IL_0000: call System.Int32 ::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 + System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerFunctionSignature_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerFunctionSignature_arch=Wide.verified.txt new file mode 100644 index 00000000..7b6541a7 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.PointerFunctionSignature_arch=Wide.verified.txt @@ -0,0 +1,6 @@ +Module: Primary + Type: + Methods: + System.Int32 ::foo(System.Void* x) + IL_0000: ldc.i4.0 + IL_0001: ret diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StaticArray_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StaticArray_arch=Wide.verified.txt index 4e864d80..9c6ad92d 100644 --- a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StaticArray_arch=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StaticArray_arch=Wide.verified.txt @@ -1,35 +1,34 @@ -System.Int32 ::main() - Locals: - System.Int32* V_0 - IL_0000: ldc.i4 2400 - IL_0005: conv.u - IL_0006: localloc - IL_0008: stloc.0 - IL_0009: ldloc.0 - IL_000a: conv.u - IL_000b: ldc.i4.8 - IL_000c: ldc.i4 299 - IL_0011: mul - IL_0012: conv.i - IL_0013: add - IL_0014: ldc.i4.0 - IL_0015: stind.i - IL_0016: ldloc.0 - IL_0017: conv.u - IL_0018: ldc.i4.8 - IL_0019: ldc.i4 299 - IL_001e: mul - IL_001f: conv.i - IL_0020: add - IL_0021: ldind.i - IL_0022: ret +Module: Primary + Type: + Methods: + System.Int32 ::main() + Locals: + System.Int32* V_0 + IL_0000: ldc.i4 2400 + IL_0005: conv.u + IL_0006: localloc + IL_0008: stloc.0 + IL_0009: ldloc.0 + IL_000a: ldc.i4.8 + IL_000b: ldc.i4 299 + IL_0010: mul + IL_0011: add + IL_0012: ldc.i4.0 + IL_0013: stind.i + IL_0014: ldloc.0 + IL_0015: ldc.i4.8 + IL_0016: ldc.i4 299 + IL_001b: mul + IL_001c: add + IL_001d: ldind.i + IL_001e: ret -System.Int32 ::() - Locals: - System.Int32 V_0 - IL_0000: call System.Int32 ::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 + System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructArray_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructArray_arch=Wide.verified.txt index 31df5fc5..a9c99e26 100644 --- a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructArray_arch=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructArray_arch=Wide.verified.txt @@ -1,19 +1,26 @@ -System.Int32 ::main() - Locals: - foo* V_0 - IL_0000: ldc.i4.s 24 - IL_0002: conv.u - IL_0003: localloc - IL_0005: stloc.0 - IL_0006: ldc.i4.0 - IL_0007: ret +Module: Primary + Type: + Methods: + System.Int32 ::main() + Locals: + foo* V_0 + IL_0000: ldc.i4.s 24 + IL_0002: conv.u + IL_0003: localloc + IL_0005: stloc.0 + IL_0006: ldc.i4.0 + IL_0007: ret -System.Int32 ::() - Locals: - System.Int32 V_0 - IL_0000: call System.Int32 ::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 + System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 + + Type: foo + Fields: + System.Byte* foo::ptr diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt index c53b7f26..eeeda309 100644 --- a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt @@ -1,9 +1,9 @@ Module: Primary Type: - Type: foo + Type: foo Nested types: - Type: foo/x + Type: foo/x Pack: 0 Size: 8 Custom attributes: @@ -11,9 +11,9 @@ - UnsafeValueTypeAttribute() Fields: - System.Byte* foo/x::FixedElementField + System.Byte* foo/x::FixedElementField Fields: - foo/x foo::x + foo/x foo::x Custom attributes: - FixedBufferAttribute(System.Byte*, 8) From e74fb50e4578bb052c597dede7ec5dcd9b1b0cad Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 17 Aug 2023 21:53:01 +0200 Subject: [PATCH 15/53] (#356) Test fixes --- ...sts.FunctionPointerParameter_arch=Wide.verified.txt | 10 ++++++++++ ....FunctionPointerStructMember_arch=Wide.verified.txt | 6 ++++++ Cesium.CodeGen/Ir/Types/FunctionType.cs | 3 --- 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Wide.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerStructMember_arch=Wide.verified.txt diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Wide.verified.txt new file mode 100644 index 00000000..d6cb9eeb --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerParameter_arch=Wide.verified.txt @@ -0,0 +1,10 @@ +Module: Primary + Type: + Methods: + System.Int32 ::foo(method System.Void *(System.Int32,System.Int32) x) + IL_0000: ldc.i4.0 + IL_0001: ret + + System.Int32 ::v_foo(method System.Void *() x) + IL_0000: ldc.i4.0 + IL_0001: ret diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerStructMember_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerStructMember_arch=Wide.verified.txt new file mode 100644 index 00000000..1b1914a3 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.FunctionPointerStructMember_arch=Wide.verified.txt @@ -0,0 +1,6 @@ +Module: Primary + Type: + + Type: Foo + Fields: + method System.Void *(System.Int32) Foo::x diff --git a/Cesium.CodeGen/Ir/Types/FunctionType.cs b/Cesium.CodeGen/Ir/Types/FunctionType.cs index 6869289a..fb187889 100644 --- a/Cesium.CodeGen/Ir/Types/FunctionType.cs +++ b/Cesium.CodeGen/Ir/Types/FunctionType.cs @@ -11,9 +11,6 @@ public TypeReference Resolve(TranslationUnitContext context) => public TypeReference ResolvePointer(TranslationUnitContext context) { - if (context.AssemblyContext.ArchitectureSet == TargetArchitectureSet.Wide) - throw new WipException(WipException.ToDo, $"Cannot resolve function pointer {this} in wide architecture."); - var pointer = new FunctionPointerType { ReturnType = ReturnType.Resolve(context) From a43396e187a9beb6006e715c52bb5ab8329e83ac Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 17 Aug 2023 22:01:45 +0200 Subject: [PATCH 16/53] (#354) Tests: add .NET interop tests --- .../CodeGenNetInteropTests.cs | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs diff --git a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs new file mode 100644 index 00000000..460f10da --- /dev/null +++ b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs @@ -0,0 +1,97 @@ +namespace Cesium.CodeGen.Tests; + +public class CodeGenNetInteropTests +{ + private static void DoTest(TargetArchitectureSet architecture, string cSharpCode, string cCode) + { + Assert.False(true, + "TODO: Compile .NET Assembly, compile C assembly with reference to .NET, dump the byte code"); + } + + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Wide)] + public void PointerInterop(TargetArchitectureSet architecture) => DoTest( + architecture, + @"public static class Test +{ + public static int Func(int* ptr) => 1; +} +", """ +__cli_import("Test::Func") +int Func(int *ptr); + +int main(void) +{ + int x = 0; + return Func(&x); +} +"""); + + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Wide)] + public void CPtrInterop(TargetArchitectureSet architecture) => DoTest( + architecture, + @"using Cesium.Runtime; +public static class Test +{ + public static int Func(CPtr ptr) => 1; +} +", """ + __cli_import("Test::Func") + int Func(int *ptr); + + int main(void) + { + int x = 0; + return Func(&x); + } + """); + + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Wide)] + public void VPtrInterop(TargetArchitectureSet architecture) => DoTest( + architecture, + @"using Cesium.Runtime; +public static class Test +{ + public static int Func(CPtr ptr) => 1; +} +", """ + __cli_import("Test::Func") + int Func(int *ptr); + + int main(void) + { + int x = 0; + return Func(&x); + } + """); + + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Wide)] + public void FPtrInterop(TargetArchitectureSet architecture) => DoTest( + architecture, + @"using Cesium.Runtime; +public static class Test +{ + public static int Func(FPtr> ptr) => 1; +} +", """ + __cli_import("Test::Func") + int Func(int (*ptr)()); + + int myFunc() + { + return 0; + } + + int main(void) + { + return Func(&myFunc); + } + """); +} From a1689a2d3b0874e552dca6409fe1d0ddc59ee352 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 17 Aug 2023 22:11:16 +0200 Subject: [PATCH 17/53] (#354) Tests: add .NET pointer interop tests --- Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs index 460f10da..36c5e289 100644 --- a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs @@ -1,8 +1,13 @@ +using System.Diagnostics.CodeAnalysis; + namespace Cesium.CodeGen.Tests; public class CodeGenNetInteropTests { - private static void DoTest(TargetArchitectureSet architecture, string cSharpCode, string cCode) + private static void DoTest( + TargetArchitectureSet architecture, + [StringSyntax("csharp")] string cSharpCode, + [StringSyntax("cpp")] string cCode) { Assert.False(true, "TODO: Compile .NET Assembly, compile C assembly with reference to .NET, dump the byte code"); From b97e32aa30e8356faef55900c379b6606d466d16 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sat, 19 Aug 2023 22:55:58 +0200 Subject: [PATCH 18/53] (#356) Refactor CodeGenNetInteropTests --- .../CodeGenNetInteropTests.cs | 23 ++++++++++----- Cesium.CodeGen.Tests/CodeGenTestBase.cs | 29 ++++++++++++------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs index 36c5e289..7d3b5022 100644 --- a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs @@ -2,21 +2,28 @@ namespace Cesium.CodeGen.Tests; -public class CodeGenNetInteropTests +public class CodeGenNetInteropTests : CodeGenTestBase { - private static void DoTest( + private static Task DoTest( TargetArchitectureSet architecture, [StringSyntax("csharp")] string cSharpCode, [StringSyntax("cpp")] string cCode) { - Assert.False(true, - "TODO: Compile .NET Assembly, compile C assembly with reference to .NET, dump the byte code"); + var cSharpAssemblyPath = CompileCSharpAssembly(cSharpCode); + var cesiumAssembly = GenerateAssembly(runtime: null, arch: architecture, sources: new[]{cCode}, referencePaths: new[] { cSharpAssemblyPath }); + return VerifyTypes(cesiumAssembly, architecture); + } + + private static string CompileCSharpAssembly(string cSharpCode) + { + Assert.True(false, "TODO: Compile .NET Assembly"); + return null!; } [Theory] [InlineData(TargetArchitectureSet.Dynamic)] [InlineData(TargetArchitectureSet.Wide)] - public void PointerInterop(TargetArchitectureSet architecture) => DoTest( + public Task PointerInterop(TargetArchitectureSet architecture) => DoTest( architecture, @"public static class Test { @@ -36,7 +43,7 @@ int main(void) [Theory] [InlineData(TargetArchitectureSet.Dynamic)] [InlineData(TargetArchitectureSet.Wide)] - public void CPtrInterop(TargetArchitectureSet architecture) => DoTest( + public Task CPtrInterop(TargetArchitectureSet architecture) => DoTest( architecture, @"using Cesium.Runtime; public static class Test @@ -57,7 +64,7 @@ int main(void) [Theory] [InlineData(TargetArchitectureSet.Dynamic)] [InlineData(TargetArchitectureSet.Wide)] - public void VPtrInterop(TargetArchitectureSet architecture) => DoTest( + public Task VPtrInterop(TargetArchitectureSet architecture) => DoTest( architecture, @"using Cesium.Runtime; public static class Test @@ -78,7 +85,7 @@ int main(void) [Theory] [InlineData(TargetArchitectureSet.Dynamic)] [InlineData(TargetArchitectureSet.Wide)] - public void FPtrInterop(TargetArchitectureSet architecture) => DoTest( + public Task FPtrInterop(TargetArchitectureSet architecture) => DoTest( architecture, @"using Cesium.Runtime; public static class Test diff --git a/Cesium.CodeGen.Tests/CodeGenTestBase.cs b/Cesium.CodeGen.Tests/CodeGenTestBase.cs index ab3cfe8f..918b6124 100644 --- a/Cesium.CodeGen.Tests/CodeGenTestBase.cs +++ b/Cesium.CodeGen.Tests/CodeGenTestBase.cs @@ -14,19 +14,24 @@ namespace Cesium.CodeGen.Tests; [UseInvariantCulture] public abstract class CodeGenTestBase : VerifyTestBase { - protected static AssemblyDefinition GenerateAssembly(TargetRuntimeDescriptor? runtime, params string[] sources) - { - var context = CreateAssembly(runtime); - GenerateCode(context, sources); - return EmitAssembly(context); - } + protected static AssemblyDefinition GenerateAssembly(TargetRuntimeDescriptor? runtime, params string[] sources) => + GenerateAssembly(sources, runtime, TargetArchitectureSet.Dynamic, "", "", Array.Empty()); protected static AssemblyDefinition GenerateAssembly( TargetRuntimeDescriptor? runtime, TargetArchitectureSet arch = TargetArchitectureSet.Dynamic, string @namespace = "", - string globalTypeFqn = "", params string[] sources) + string globalTypeFqn = "", params string[] sources) => + GenerateAssembly(sources, runtime, arch, @namespace, globalTypeFqn, Array.Empty()); + + protected static AssemblyDefinition GenerateAssembly( + string[] sources, + TargetRuntimeDescriptor? runtime = null, + TargetArchitectureSet arch = TargetArchitectureSet.Dynamic, + string @namespace = "", + string globalTypeFqn = "", + string[]? referencePaths = null) { - var context = CreateAssembly(runtime, arch, @namespace: @namespace, globalTypeFqn: globalTypeFqn); + var context = CreateAssembly(runtime, arch, @namespace: @namespace, globalTypeFqn: globalTypeFqn, referencePaths); GenerateCode(context, sources); return EmitAssembly(context); } @@ -58,15 +63,19 @@ private static AssemblyContext CreateAssembly( TargetRuntimeDescriptor? targetRuntime, TargetArchitectureSet targetArchitectureSet = TargetArchitectureSet.Dynamic, string @namespace = "", - string globalTypeFqn = "") + string globalTypeFqn = "", + string[]? referencePaths = null) { + var allReferences = (referencePaths ?? Array.Empty()).ToList(); + allReferences.Insert(0, typeof(Console).Assembly.Location); + CompilationOptions compilationOptions = new CompilationOptions( targetRuntime ?? TargetRuntimeDescriptor.Net60, targetArchitectureSet, ModuleKind.Console, typeof(Math).Assembly.Location, typeof(Runtime.RuntimeHelpers).Assembly.Location, - new[] { typeof(Console).Assembly.Location }, + allReferences, @namespace, globalTypeFqn, Array.Empty()); From e63f01a34ecb66eb45b374c8fd06f464f7fb798d Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Mon, 21 Aug 2023 23:32:32 +0200 Subject: [PATCH 19/53] (#354) Tests: add CSharpCompilationUtil --- .../CodeGenNetInteropTests.cs | 23 +++++--- Cesium.CodeGen.Tests/CodeGenTestBase.cs | 2 +- .../Cesium.IntegrationTests.csproj | 4 ++ .../IntegrationTestContext.cs | 1 + .../IntegrationTestRunner.cs | 2 +- Cesium.IntegrationTests/WindowsEnvUtil.cs | 1 + .../CSharpCompilationUtil.cs | 55 +++++++++++++++++++ .../Cesium.Test.Framework.csproj | 2 + .../ExecUtil.cs | 5 +- 9 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 Cesium.Test.Framework/CSharpCompilationUtil.cs rename {Cesium.IntegrationTests => Cesium.Test.Framework}/ExecUtil.cs (93%) diff --git a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs index 7d3b5022..b8e29d4e 100644 --- a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs @@ -1,23 +1,28 @@ using System.Diagnostics.CodeAnalysis; +using Cesium.Test.Framework; +using Xunit.Abstractions; namespace Cesium.CodeGen.Tests; public class CodeGenNetInteropTests : CodeGenTestBase { - private static Task DoTest( + private readonly ITestOutputHelper _output; + public CodeGenNetInteropTests(ITestOutputHelper output) + { + _output = output; + } + + private async Task DoTest( TargetArchitectureSet architecture, [StringSyntax("csharp")] string cSharpCode, [StringSyntax("cpp")] string cCode) { - var cSharpAssemblyPath = CompileCSharpAssembly(cSharpCode); + var cSharpAssemblyPath = await CSharpCompilationUtil.CompileCSharpAssembly( + _output, + CSharpCompilationUtil.DefaultRuntime, + cSharpCode); var cesiumAssembly = GenerateAssembly(runtime: null, arch: architecture, sources: new[]{cCode}, referencePaths: new[] { cSharpAssemblyPath }); - return VerifyTypes(cesiumAssembly, architecture); - } - - private static string CompileCSharpAssembly(string cSharpCode) - { - Assert.True(false, "TODO: Compile .NET Assembly"); - return null!; + await VerifyTypes(cesiumAssembly, architecture); } [Theory] diff --git a/Cesium.CodeGen.Tests/CodeGenTestBase.cs b/Cesium.CodeGen.Tests/CodeGenTestBase.cs index 918b6124..90163f26 100644 --- a/Cesium.CodeGen.Tests/CodeGenTestBase.cs +++ b/Cesium.CodeGen.Tests/CodeGenTestBase.cs @@ -70,7 +70,7 @@ private static AssemblyContext CreateAssembly( allReferences.Insert(0, typeof(Console).Assembly.Location); CompilationOptions compilationOptions = new CompilationOptions( - targetRuntime ?? TargetRuntimeDescriptor.Net60, + targetRuntime ?? CSharpCompilationUtil.DefaultRuntime, targetArchitectureSet, ModuleKind.Console, typeof(Math).Assembly.Location, diff --git a/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj b/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj index 9c68b00d..499155c8 100644 --- a/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj +++ b/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/Cesium.IntegrationTests/IntegrationTestContext.cs b/Cesium.IntegrationTests/IntegrationTestContext.cs index 6370d852..cf10630d 100644 --- a/Cesium.IntegrationTests/IntegrationTestContext.cs +++ b/Cesium.IntegrationTests/IntegrationTestContext.cs @@ -1,4 +1,5 @@ using System.Reflection; +using Cesium.Test.Framework; using JetBrains.Annotations; using NeoSmart.AsyncLock; using Xunit.Abstractions; diff --git a/Cesium.IntegrationTests/IntegrationTestRunner.cs b/Cesium.IntegrationTests/IntegrationTestRunner.cs index abf25b67..0dc0c1d8 100644 --- a/Cesium.IntegrationTests/IntegrationTestRunner.cs +++ b/Cesium.IntegrationTests/IntegrationTestRunner.cs @@ -1,4 +1,4 @@ -using Xunit; +using Cesium.Test.Framework; using Xunit.Abstractions; namespace Cesium.IntegrationTests; diff --git a/Cesium.IntegrationTests/WindowsEnvUtil.cs b/Cesium.IntegrationTests/WindowsEnvUtil.cs index 43cba880..3fbf90ac 100644 --- a/Cesium.IntegrationTests/WindowsEnvUtil.cs +++ b/Cesium.IntegrationTests/WindowsEnvUtil.cs @@ -1,4 +1,5 @@ using System.Runtime.Versioning; +using Cesium.Test.Framework; using Microsoft.Win32; using Xunit.Abstractions; diff --git a/Cesium.Test.Framework/CSharpCompilationUtil.cs b/Cesium.Test.Framework/CSharpCompilationUtil.cs new file mode 100644 index 00000000..fcb5c78a --- /dev/null +++ b/Cesium.Test.Framework/CSharpCompilationUtil.cs @@ -0,0 +1,55 @@ +using Cesium.CodeGen; +using Xunit.Abstractions; + +namespace Cesium.Test.Framework; + +// TODO: Make a normal disposable class to delete the whole directory in the end of the test. +public static class CSharpCompilationUtil +{ + public static readonly TargetRuntimeDescriptor DefaultRuntime = TargetRuntimeDescriptor.Net60; + private const string _configuration = "Debug"; + + /// Semaphore that controls the amount of simultaneously running tests. + // TODO: Should not be static, make a fixture. + private static readonly SemaphoreSlim _testSemaphore = new(Environment.ProcessorCount); + + // TODO: Support references + public static async Task CompileCSharpAssembly( + ITestOutputHelper output, + TargetRuntimeDescriptor runtime, + string cSharpSource) + { + if (runtime != DefaultRuntime) throw new Exception($"Runtime {runtime} not supported for test compilation."); + await _testSemaphore.WaitAsync(); + try + { + var directory = Path.GetTempFileName(); + File.Delete(directory); + Directory.CreateDirectory(directory); + + var projectName = await CreateCSharpProject(output, directory); + await File.WriteAllTextAsync(Path.Combine(directory, projectName, "Program.cs"), cSharpSource); + await CompileCSharpProject(output, directory, projectName); + return Path.Combine(directory, "bin", _configuration, "net6.0", projectName + ".dll"); + } + finally + { + _testSemaphore.Release(); + } + } + + private static async Task CreateCSharpProject(ITestOutputHelper output, string directory) + { + const string projectName = "TestProject"; + await ExecUtil.RunToSuccess(output, "dotnet", directory, new[] { "new", "console", "-o", "TestProject" }); + return projectName; + } + + private static Task CompileCSharpProject(ITestOutputHelper output, string directory, string projectName) => + ExecUtil.RunToSuccess(output, "dotnet", directory, new[] + { + "build", + projectName, + "--configuration", _configuration, + }); +} diff --git a/Cesium.Test.Framework/Cesium.Test.Framework.csproj b/Cesium.Test.Framework/Cesium.Test.Framework.csproj index dea89e45..1d34a677 100644 --- a/Cesium.Test.Framework/Cesium.Test.Framework.csproj +++ b/Cesium.Test.Framework/Cesium.Test.Framework.csproj @@ -6,6 +6,7 @@ + @@ -13,6 +14,7 @@ + diff --git a/Cesium.IntegrationTests/ExecUtil.cs b/Cesium.Test.Framework/ExecUtil.cs similarity index 93% rename from Cesium.IntegrationTests/ExecUtil.cs rename to Cesium.Test.Framework/ExecUtil.cs index 6e2ed1d8..f4ef5e92 100644 --- a/Cesium.IntegrationTests/ExecUtil.cs +++ b/Cesium.Test.Framework/ExecUtil.cs @@ -1,10 +1,9 @@ using Medallion.Shell; -using Xunit; using Xunit.Abstractions; -namespace Cesium.IntegrationTests; +namespace Cesium.Test.Framework; -internal static class ExecUtil +public static class ExecUtil { public static async Task RunToSuccess( ITestOutputHelper? output, From 95a209d7fb0d69e836324c537bc2634c5e24bde4 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Fri, 25 Aug 2023 23:06:29 +0200 Subject: [PATCH 20/53] (#354) Tests: compile C# projects with unsafe enabled --- .../CodeGenNetInteropTests.cs | 2 +- .../IntegrationTestContext.cs | 27 ++++----------- .../IntegrationTestRunner.cs | 8 ++--- .../CSharpCompilationUtil.cs | 33 +++++++++++++++++-- Cesium.Test.Framework/TestStructureUtil.cs | 23 +++++++++++++ Cesium.sln.DotSettings | 1 + 6 files changed, 67 insertions(+), 27 deletions(-) create mode 100644 Cesium.Test.Framework/TestStructureUtil.cs diff --git a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs index b8e29d4e..b7ec69c9 100644 --- a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs @@ -30,7 +30,7 @@ private async Task DoTest( [InlineData(TargetArchitectureSet.Wide)] public Task PointerInterop(TargetArchitectureSet architecture) => DoTest( architecture, - @"public static class Test + @"public static unsafe class Test { public static int Func(int* ptr) => 1; } diff --git a/Cesium.IntegrationTests/IntegrationTestContext.cs b/Cesium.IntegrationTests/IntegrationTestContext.cs index cf10630d..04d2b47b 100644 --- a/Cesium.IntegrationTests/IntegrationTestContext.cs +++ b/Cesium.IntegrationTests/IntegrationTestContext.cs @@ -1,4 +1,3 @@ -using System.Reflection; using Cesium.Test.Framework; using JetBrains.Annotations; using NeoSmart.AsyncLock; @@ -9,7 +8,6 @@ namespace Cesium.IntegrationTests; [UsedImplicitly] public class IntegrationTestContext : IAsyncDisposable { - public static readonly string SolutionRootPath = GetSolutionRoot(); public const string BuildConfiguration = "Release"; /// Semaphore that controls the amount of simultaneously running tests. @@ -73,37 +71,26 @@ private async Task InitializeOnce(ITestOutputHelper output) public async ValueTask DisposeAsync() { - await ExecUtil.RunToSuccess(null, "dotnet", SolutionRootPath, new[] + await ExecUtil.RunToSuccess(null, "dotnet", TestStructureUtil.SolutionRootPath, new[] { "build-server", "shutdown" }); } - private static string GetSolutionRoot() - { - var assemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - var currentDirectory = assemblyDirectory; - while (currentDirectory != null) - { - if (File.Exists(Path.Combine(currentDirectory, "Cesium.sln"))) - return currentDirectory; - - currentDirectory = Path.GetDirectoryName(currentDirectory); - } - - throw new Exception($"Could not find the solution directory going up from directory \"{assemblyDirectory}\"."); - } - private async Task BuildRuntime(ITestOutputHelper output) { - var runtimeProjectFile = Path.Combine(SolutionRootPath, "Cesium.Runtime/Cesium.Runtime.csproj"); + var runtimeProjectFile = Path.Combine( + TestStructureUtil.SolutionRootPath, + "Cesium.Runtime/Cesium.Runtime.csproj"); await BuildDotNetProject(output, runtimeProjectFile); } private async Task BuildCompiler(ITestOutputHelper output) { - var compilerProjectFile = Path.Combine(SolutionRootPath, "Cesium.Compiler/Cesium.Compiler.csproj"); + var compilerProjectFile = Path.Combine( + TestStructureUtil.SolutionRootPath, + "Cesium.Compiler/Cesium.Compiler.csproj"); await BuildDotNetProject(output, compilerProjectFile); } diff --git a/Cesium.IntegrationTests/IntegrationTestRunner.cs b/Cesium.IntegrationTests/IntegrationTestRunner.cs index 0dc0c1d8..9ede6014 100644 --- a/Cesium.IntegrationTests/IntegrationTestRunner.cs +++ b/Cesium.IntegrationTests/IntegrationTestRunner.cs @@ -20,7 +20,7 @@ public Task InitializeAsync() => public static IEnumerable TestCaseProvider() { - var testCaseDirectory = Path.Combine(IntegrationTestContext.SolutionRootPath, "Cesium.IntegrationTests"); + var testCaseDirectory = Path.Combine(TestStructureUtil.SolutionRootPath, "Cesium.IntegrationTests"); var cFiles = Directory.EnumerateFileSystemEntries(testCaseDirectory, "*.c", SearchOption.AllDirectories); return cFiles .Where(file => !file.EndsWith(".ignore.c")) @@ -61,7 +61,7 @@ private async Task DoTest(string relativeSourcePath, TargetFramework targetFrame Directory.CreateDirectory(objDirPath); var sourceFilePath = Path.Combine( - IntegrationTestContext.SolutionRootPath, + TestStructureUtil.SolutionRootPath, "Cesium.IntegrationTests", relativeSourcePath); @@ -179,7 +179,7 @@ private async Task BuildExecutableWithCesium( "run", "--no-build", "--configuration", IntegrationTestContext.BuildConfiguration, - "--project", Path.Combine(IntegrationTestContext.SolutionRootPath, "Cesium.Compiler"), + "--project", Path.Combine(TestStructureUtil.SolutionRootPath, "Cesium.Compiler"), "--", "--nologo", sourceFilePath, @@ -192,7 +192,7 @@ private async Task BuildExecutableWithCesium( { var coreLibPath = WindowsEnvUtil.MsCorLibPath; var runtimeLibPath = Path.Combine( - IntegrationTestContext.SolutionRootPath, + TestStructureUtil.SolutionRootPath, "Cesium.Runtime/bin", IntegrationTestContext.BuildConfiguration, "netstandard2.0/Cesium.Runtime.dll" diff --git a/Cesium.Test.Framework/CSharpCompilationUtil.cs b/Cesium.Test.Framework/CSharpCompilationUtil.cs index fcb5c78a..658bbb7b 100644 --- a/Cesium.Test.Framework/CSharpCompilationUtil.cs +++ b/Cesium.Test.Framework/CSharpCompilationUtil.cs @@ -1,3 +1,5 @@ +using System.Xml.Linq; +using System.Xml.XPath; using Cesium.CodeGen; using Xunit.Abstractions; @@ -8,6 +10,7 @@ public static class CSharpCompilationUtil { public static readonly TargetRuntimeDescriptor DefaultRuntime = TargetRuntimeDescriptor.Net60; private const string _configuration = "Debug"; + private const string _targetRuntime = "net6.0"; /// Semaphore that controls the amount of simultaneously running tests. // TODO: Should not be static, make a fixture. @@ -30,7 +33,7 @@ public static async Task CompileCSharpAssembly( var projectName = await CreateCSharpProject(output, directory); await File.WriteAllTextAsync(Path.Combine(directory, projectName, "Program.cs"), cSharpSource); await CompileCSharpProject(output, directory, projectName); - return Path.Combine(directory, "bin", _configuration, "net6.0", projectName + ".dll"); + return Path.Combine(directory, "bin", _configuration, _targetRuntime, projectName + ".dll"); } finally { @@ -41,10 +44,36 @@ public static async Task CompileCSharpAssembly( private static async Task CreateCSharpProject(ITestOutputHelper output, string directory) { const string projectName = "TestProject"; - await ExecUtil.RunToSuccess(output, "dotnet", directory, new[] { "new", "console", "-o", "TestProject" }); + await ExecUtil.RunToSuccess(output, "dotnet", directory, new[] { "new", "classlib", "-o", "TestProject" }); + var projectFilePath = Path.Combine(directory, projectName, $"{projectName}.csproj"); + XDocument csProj; + await using (var projectFileStream = new FileStream(projectFilePath, FileMode.Open, FileAccess.Read)) + { + csProj = await XDocument.LoadAsync(projectFileStream, LoadOptions.None, CancellationToken.None); + } + + var project = csProj.XPathSelectElement("/Project")!; + project.Add(new XElement("PropertyGroup", + new XElement(new XElement("AllowUnsafeBlocks", "true")))); + + var runtimeLibraryPath = GetCesiumRuntimeLibraryPath(); + project.Add(new XElement("ItemGroup", + new XElement("Reference", new XAttribute("Include", runtimeLibraryPath)))); + + await using var outputStream = new FileStream(projectFilePath, FileMode.Truncate, FileAccess.Write); + await csProj.SaveAsync(outputStream, SaveOptions.None, CancellationToken.None); + return projectName; } + private static string GetCesiumRuntimeLibraryPath() => Path.Combine( + TestStructureUtil.SolutionRootPath, + "Cesium.Runtime", + "bin", + _configuration, + _targetRuntime, + "Cesium.Runtime.dll"); + private static Task CompileCSharpProject(ITestOutputHelper output, string directory, string projectName) => ExecUtil.RunToSuccess(output, "dotnet", directory, new[] { diff --git a/Cesium.Test.Framework/TestStructureUtil.cs b/Cesium.Test.Framework/TestStructureUtil.cs new file mode 100644 index 00000000..fb4db200 --- /dev/null +++ b/Cesium.Test.Framework/TestStructureUtil.cs @@ -0,0 +1,23 @@ +using System.Reflection; + +namespace Cesium.Test.Framework; + +public static class TestStructureUtil +{ + public static readonly string SolutionRootPath = GetSolutionRoot(); + + private static string GetSolutionRoot() + { + var assemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var currentDirectory = assemblyDirectory; + while (currentDirectory != null) + { + if (File.Exists(Path.Combine(currentDirectory, "Cesium.sln"))) + return currentDirectory; + + currentDirectory = Path.GetDirectoryName(currentDirectory); + } + + throw new Exception($"Could not find the solution directory going up from directory \"{assemblyDirectory}\"."); + } +} diff --git a/Cesium.sln.DotSettings b/Cesium.sln.DotSettings index aa059e8c..0775b5cf 100644 --- a/Cesium.sln.DotSettings +++ b/Cesium.sln.DotSettings @@ -2,6 +2,7 @@ True True + True True True True From 9a5e3a41fa2484f9a83afe0a855e95bb0db23f16 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 27 Aug 2023 23:19:31 +0200 Subject: [PATCH 21/53] (#354) Finish .NET compilation support for tests --- .../CSharpCompilationUtil.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Cesium.Test.Framework/CSharpCompilationUtil.cs b/Cesium.Test.Framework/CSharpCompilationUtil.cs index 658bbb7b..958caf63 100644 --- a/Cesium.Test.Framework/CSharpCompilationUtil.cs +++ b/Cesium.Test.Framework/CSharpCompilationUtil.cs @@ -11,6 +11,7 @@ public static class CSharpCompilationUtil public static readonly TargetRuntimeDescriptor DefaultRuntime = TargetRuntimeDescriptor.Net60; private const string _configuration = "Debug"; private const string _targetRuntime = "net6.0"; + private const string _projectName = "TestProject"; /// Semaphore that controls the amount of simultaneously running tests. // TODO: Should not be static, make a fixture. @@ -30,10 +31,10 @@ public static async Task CompileCSharpAssembly( File.Delete(directory); Directory.CreateDirectory(directory); - var projectName = await CreateCSharpProject(output, directory); - await File.WriteAllTextAsync(Path.Combine(directory, projectName, "Program.cs"), cSharpSource); - await CompileCSharpProject(output, directory, projectName); - return Path.Combine(directory, "bin", _configuration, _targetRuntime, projectName + ".dll"); + var projectDirectory = await CreateCSharpProject(output, directory); + await File.WriteAllTextAsync(Path.Combine(projectDirectory, "Program.cs"), cSharpSource); + await CompileCSharpProject(output, directory, _projectName); + return Path.Combine(projectDirectory, "bin", _configuration, _targetRuntime, _projectName + ".dll"); } finally { @@ -43,9 +44,13 @@ public static async Task CompileCSharpAssembly( private static async Task CreateCSharpProject(ITestOutputHelper output, string directory) { - const string projectName = "TestProject"; - await ExecUtil.RunToSuccess(output, "dotnet", directory, new[] { "new", "classlib", "-o", "TestProject" }); - var projectFilePath = Path.Combine(directory, projectName, $"{projectName}.csproj"); + await ExecUtil.RunToSuccess( + output, + "dotnet", + directory, + new[] { "new", "classlib", "--framework", _targetRuntime, "--output", _projectName }); + var projectDirectory = Path.Combine(directory, _projectName); + var projectFilePath = Path.Combine(projectDirectory, $"{_projectName}.csproj"); XDocument csProj; await using (var projectFileStream = new FileStream(projectFilePath, FileMode.Open, FileAccess.Read)) { @@ -63,7 +68,7 @@ private static async Task CreateCSharpProject(ITestOutputHelper output, await using var outputStream = new FileStream(projectFilePath, FileMode.Truncate, FileAccess.Write); await csProj.SaveAsync(outputStream, SaveOptions.None, CancellationToken.None); - return projectName; + return projectDirectory; } private static string GetCesiumRuntimeLibraryPath() => Path.Combine( From 2424b21e88eb02a24c791308e39f4d23a8234d3f Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Tue, 29 Aug 2023 22:02:01 +0200 Subject: [PATCH 22/53] (#354) Tests: add a test for CPtr in struct generation --- .../ArchitectureDependentTypeTests.cs | 16 ++++++++++++-- ...ctWithPointerArray_arch=Bit32.verified.txt | 19 +++++++++++++++++ ...ctWithPointerArray_arch=Bit64.verified.txt | 19 +++++++++++++++++ ...uctWithPointerArray_arch=Wide.verified.txt | 19 +++++++++++++++++ ....StructWithPointer_arch=Bit32.verified.txt | 15 +------------ ....StructWithPointer_arch=Bit64.verified.txt | 15 +------------ ...tructWithPointer_arch=Dynamic.verified.txt | 6 ++++++ ...s.StructWithPointer_arch=Wide.verified.txt | 15 +------------ ...rInterop_architecture=Dynamic.verified.txt | 21 +++++++++++++++++++ ...nterInterop_architecture=Wide.verified.txt | 21 +++++++++++++++++++ 10 files changed, 122 insertions(+), 44 deletions(-) create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointerArray_arch=Bit32.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointerArray_arch=Bit64.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointerArray_arch=Wide.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Dynamic.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Dynamic.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Wide.verified.txt diff --git a/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs b/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs index 93325b2f..fd07c5a9 100644 --- a/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs +++ b/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs @@ -20,15 +20,27 @@ private static Task DoTest(TargetArchitectureSet arch, string source, string @na [InlineData(TargetArchitectureSet.Bit64)] [InlineData(TargetArchitectureSet.Bit32)] [InlineData(TargetArchitectureSet.Wide)] - public Task StructWithPointer(TargetArchitectureSet arch) => DoTest(arch, """ + public Task StructWithPointerArray(TargetArchitectureSet arch) => DoTest(arch, """ typedef struct { char *x[1]; } foo; """); + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Bit64)] + [InlineData(TargetArchitectureSet.Bit32)] + [InlineData(TargetArchitectureSet.Wide)] + public Task StructWithPointer(TargetArchitectureSet arch) => DoTest(arch, """ + typedef struct + { + char *x; + } foo; + """); + [Fact(DisplayName = "Struct with a fixed array of a pointer type isn't supported for dynamic architecture")] - public void StructWithPointerDynamic() => DoesNotCompile(""" + public void StructWithPointerArrayDynamic() => DoesNotCompile(""" typedef struct { char *x[1]; diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointerArray_arch=Bit32.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointerArray_arch=Bit32.verified.txt new file mode 100644 index 00000000..b9e8b75d --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointerArray_arch=Bit32.verified.txt @@ -0,0 +1,19 @@ +Module: Primary + Type: + + Type: foo + Nested types: + Type: foo/x + Pack: 0 + Size: 4 + Custom attributes: + - CompilerGeneratedAttribute() + - UnsafeValueTypeAttribute() + + Fields: + System.Byte* foo/x::FixedElementField + Fields: + foo/x foo::x + Custom attributes: + - FixedBufferAttribute(System.Byte*, 4) + diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointerArray_arch=Bit64.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointerArray_arch=Bit64.verified.txt new file mode 100644 index 00000000..eeeda309 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointerArray_arch=Bit64.verified.txt @@ -0,0 +1,19 @@ +Module: Primary + Type: + + Type: foo + Nested types: + Type: foo/x + Pack: 0 + Size: 8 + Custom attributes: + - CompilerGeneratedAttribute() + - UnsafeValueTypeAttribute() + + Fields: + System.Byte* foo/x::FixedElementField + Fields: + foo/x foo::x + Custom attributes: + - FixedBufferAttribute(System.Byte*, 8) + diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointerArray_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointerArray_arch=Wide.verified.txt new file mode 100644 index 00000000..eeeda309 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointerArray_arch=Wide.verified.txt @@ -0,0 +1,19 @@ +Module: Primary + Type: + + Type: foo + Nested types: + Type: foo/x + Pack: 0 + Size: 8 + Custom attributes: + - CompilerGeneratedAttribute() + - UnsafeValueTypeAttribute() + + Fields: + System.Byte* foo/x::FixedElementField + Fields: + foo/x foo::x + Custom attributes: + - FixedBufferAttribute(System.Byte*, 8) + diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Bit32.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Bit32.verified.txt index b9e8b75d..8b2220d5 100644 --- a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Bit32.verified.txt +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Bit32.verified.txt @@ -2,18 +2,5 @@ Type: Type: foo - Nested types: - Type: foo/x - Pack: 0 - Size: 4 - Custom attributes: - - CompilerGeneratedAttribute() - - UnsafeValueTypeAttribute() - - Fields: - System.Byte* foo/x::FixedElementField Fields: - foo/x foo::x - Custom attributes: - - FixedBufferAttribute(System.Byte*, 4) - + System.Byte* foo::x diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Bit64.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Bit64.verified.txt index eeeda309..8b2220d5 100644 --- a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Bit64.verified.txt +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Bit64.verified.txt @@ -2,18 +2,5 @@ Type: Type: foo - Nested types: - Type: foo/x - Pack: 0 - Size: 8 - Custom attributes: - - CompilerGeneratedAttribute() - - UnsafeValueTypeAttribute() - - Fields: - System.Byte* foo/x::FixedElementField Fields: - foo/x foo::x - Custom attributes: - - FixedBufferAttribute(System.Byte*, 8) - + System.Byte* foo::x diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Dynamic.verified.txt new file mode 100644 index 00000000..8b2220d5 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Dynamic.verified.txt @@ -0,0 +1,6 @@ +Module: Primary + Type: + + Type: foo + Fields: + System.Byte* foo::x diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt index eeeda309..602065d4 100644 --- a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt @@ -2,18 +2,5 @@ Type: Type: foo - Nested types: - Type: foo/x - Pack: 0 - Size: 8 - Custom attributes: - - CompilerGeneratedAttribute() - - UnsafeValueTypeAttribute() - - Fields: - System.Byte* foo/x::FixedElementField Fields: - foo/x foo::x - Custom attributes: - - FixedBufferAttribute(System.Byte*, 8) - + Cesium.Runtime.CPtr foo::x diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Dynamic.verified.txt new file mode 100644 index 00000000..6f882fdc --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Dynamic.verified.txt @@ -0,0 +1,21 @@ +Module: Primary + Type: + Methods: + System.Int32 ::main() + Locals: + System.Int32 V_0 + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: call System.Int32 Test::Func(System.Int32*) + IL_0009: ret + + System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Wide.verified.txt new file mode 100644 index 00000000..6f882fdc --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Wide.verified.txt @@ -0,0 +1,21 @@ +Module: Primary + Type: + Methods: + System.Int32 ::main() + Locals: + System.Int32 V_0 + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: call System.Int32 Test::Func(System.Int32*) + IL_0009: ret + + System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 From b4ef7d7bcfe325758d7ffc507b21feebb8c1fe96 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Tue, 29 Aug 2023 22:31:24 +0200 Subject: [PATCH 23/53] (#354) Basic support for new runtime wide pointer wrappers --- Cesium.CodeGen/Ir/Types/CTypeSystem.cs | 12 +++++++++ Cesium.CodeGen/Ir/Types/IType.cs | 8 +++++- Cesium.CodeGen/Ir/Types/PointerType.cs | 27 ++++++++++++++----- .../Ir/Types/ResolvedPointerType.cs | 10 +++++++ 4 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 Cesium.CodeGen/Ir/Types/ResolvedPointerType.cs diff --git a/Cesium.CodeGen/Ir/Types/CTypeSystem.cs b/Cesium.CodeGen/Ir/Types/CTypeSystem.cs index db9651fb..6fa10976 100644 --- a/Cesium.CodeGen/Ir/Types/CTypeSystem.cs +++ b/Cesium.CodeGen/Ir/Types/CTypeSystem.cs @@ -1,5 +1,6 @@ using Cesium.CodeGen.Extensions; using Cesium.Core; +using Mono.Cecil; namespace Cesium.CodeGen.Ir.Types; @@ -21,6 +22,17 @@ internal class CTypeSystem public IType NativeInt { get; } = new PrimitiveType(PrimitiveTypeKind.NativeInt); public IType NativeUInt { get; } = new PrimitiveType(PrimitiveTypeKind.NativeUInt); + public IType RuntimeVoidPtr { get; } + + public CTypeSystem(AssemblyDefinition cesiumRuntimeAssembly) + { + TypeReference GetRuntimeType(string name) => + cesiumRuntimeAssembly.GetType(name) ?? + throw new AssertException($"Could not find type {name} in the runtime assembly."); + + RuntimeVoidPtr = new ResolvedPointerType(GetRuntimeType("Cesium.Runtime.VoidPtr")); + } + public bool IsConversionAvailable(IType type, IType targetType) { if (type.IsEqualTo(targetType) diff --git a/Cesium.CodeGen/Ir/Types/IType.cs b/Cesium.CodeGen/Ir/Types/IType.cs index 057f6239..619bd096 100644 --- a/Cesium.CodeGen/Ir/Types/IType.cs +++ b/Cesium.CodeGen/Ir/Types/IType.cs @@ -15,8 +15,14 @@ internal interface IType { TypeReference Resolve(TranslationUnitContext context); + /// + /// For cases when a type gets resolved differently for a type member context. For example, a pointer will + /// recursively get resolved as a CPtr on a wide architecture. + /// + TypeReference ResolveForTypeMember(TranslationUnitContext context) => Resolve(context); + FieldDefinition CreateFieldOfType(TranslationUnitContext context, TypeDefinition ownerType, string fieldName) => - new(fieldName, FieldAttributes.Public, Resolve(context)); + new(fieldName, FieldAttributes.Public, ResolveForTypeMember(context)); /// Determines the size of an object of this type in bytes, if possible. /// Target architecture set. diff --git a/Cesium.CodeGen/Ir/Types/PointerType.cs b/Cesium.CodeGen/Ir/Types/PointerType.cs index e12757e4..ea8d1136 100644 --- a/Cesium.CodeGen/Ir/Types/PointerType.cs +++ b/Cesium.CodeGen/Ir/Types/PointerType.cs @@ -8,6 +8,15 @@ namespace Cesium.CodeGen.Ir.Types; internal sealed record PointerType(IType Base) : IType { + public static int? SizeInBytes(TargetArchitectureSet arch) => arch switch + { + TargetArchitectureSet.Dynamic => null, + TargetArchitectureSet.Bit32 => 4, + TargetArchitectureSet.Bit64 => 8, + TargetArchitectureSet.Wide => 8, + _ => throw new AssertException($"Unknown architecture set: {arch}.") + }; + public TypeReference Resolve(TranslationUnitContext context) { if (Base is FunctionType ft) @@ -16,16 +25,20 @@ public TypeReference Resolve(TranslationUnitContext context) return Base.Resolve(context).MakePointerType(); } - public int? GetSizeInBytes(TargetArchitectureSet arch) => - arch switch + public TypeReference ResolveForTypeMember(TranslationUnitContext context) => + context.AssemblyContext.ArchitectureSet switch { - TargetArchitectureSet.Dynamic => null, - TargetArchitectureSet.Bit32 => 4, - TargetArchitectureSet.Bit64 => 8, - TargetArchitectureSet.Wide => 8, - _ => throw new AssertException($"Unknown architecture set: {arch}.") + TargetArchitectureSet.Wide => Base switch + { + FunctionType => context.TypeSystem.RuntimeFPtr(Base.ResolveForTypeMember(context)), + PrimitiveType { Kind: PrimitiveTypeKind.Void } => context.TypeSystem.RuntimeVoidPtr, + _ => context.TypeSystem.RuntimeCPtr(Base.ResolveForTypeMember(context)), + }, + _ => Resolve(context) }; + public int? GetSizeInBytes(TargetArchitectureSet arch) => SizeInBytes(arch); + public IExpression GetSizeInBytesExpression(TargetArchitectureSet arch) { var constSize = GetSizeInBytes(arch); diff --git a/Cesium.CodeGen/Ir/Types/ResolvedPointerType.cs b/Cesium.CodeGen/Ir/Types/ResolvedPointerType.cs new file mode 100644 index 00000000..0f371581 --- /dev/null +++ b/Cesium.CodeGen/Ir/Types/ResolvedPointerType.cs @@ -0,0 +1,10 @@ +using Cesium.CodeGen.Contexts; +using Mono.Cecil; + +namespace Cesium.CodeGen.Ir.Types; + +internal record ResolvedPointerType(TypeReference ResolvedType) : IType +{ + public TypeReference Resolve(TranslationUnitContext context) => ResolvedType; + public int? GetSizeInBytes(TargetArchitectureSet arch) => PointerType.SizeInBytes(arch); +} From 4350ca12e912ab30a8d860939b527533100f7f74 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sat, 2 Sep 2023 23:20:07 +0200 Subject: [PATCH 24/53] (#354) CodeGen: support CPtr interop on the Wide architecture --- ...s.StructWithPointer_arch=Wide.verified.txt | 2 +- .../Contexts/TranslationUnitContext.cs | 43 +++++++++++++++++-- Cesium.CodeGen/Ir/Types/CTypeSystem.cs | 12 ------ Cesium.CodeGen/Ir/Types/PointerType.cs | 6 +-- .../Ir/Types/ResolvedPointerType.cs | 10 ----- 5 files changed, 44 insertions(+), 29 deletions(-) delete mode 100644 Cesium.CodeGen/Ir/Types/ResolvedPointerType.cs diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt index 602065d4..a953cb0f 100644 --- a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithPointer_arch=Wide.verified.txt @@ -3,4 +3,4 @@ Type: foo Fields: - Cesium.Runtime.CPtr foo::x + Cesium.Runtime.CPtr`1 foo::x diff --git a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs index 8f76f076..1c56dac8 100644 --- a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs +++ b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs @@ -1,4 +1,4 @@ -using Cesium.Ast; +using System.Diagnostics; using Cesium.CodeGen.Contexts.Meta; using Cesium.CodeGen.Extensions; using Cesium.CodeGen.Ir; @@ -6,13 +6,20 @@ using Cesium.CodeGen.Ir.Types; using Cesium.Core; using Mono.Cecil; -using System.Diagnostics; +using Mono.Cecil.Rocks; using PointerType = Cesium.CodeGen.Ir.Types.PointerType; namespace Cesium.CodeGen.Contexts; -public record TranslationUnitContext(AssemblyContext AssemblyContext, string Name) +public class TranslationUnitContext { + private const string _cPtrFullTypeName = "Cesium.Runtime.CPtr`1"; + private const string _voidPtrFullTypeName = "Cesium.Runtime.VoidPtr"; + private const string _funcPtrFullTypeName = "Cesium.Runtime.FuncPtr`1"; + + public AssemblyContext AssemblyContext { get; } + public string Name { get; } + public AssemblyDefinition Assembly => AssemblyContext.Assembly; public ModuleDefinition Module => AssemblyContext.Module; public TypeSystem TypeSystem => Module.TypeSystem; @@ -26,6 +33,36 @@ public record TranslationUnitContext(AssemblyContext AssemblyContext, string Nam 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(_cPtrFullTypeName)); + RuntimeVoidPtr = Module.ImportReference(GetRuntimeType(_voidPtrFullTypeName)); + _runtimeFuncPtr = Module.ImportReference(GetRuntimeType(_funcPtrFullTypeName)); + } + + public TypeReference RuntimeCPtr(TypeReference typeReference) + { + return _runtimeCPtr.MakeGenericInstanceType(typeReference); + } + + public TypeReference RuntimeFuncPtr(TypeReference typeReference) + { + // TODO: Resolve a corresponding delegate type + // return _runtimeFuncPtr.MakeGenericInstanceType(delegateTypeReference); + throw new WipException(WipException.ToDo, "Resolve a delegate for FPtr"); + } + /// /// 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/Ir/Types/CTypeSystem.cs b/Cesium.CodeGen/Ir/Types/CTypeSystem.cs index 6fa10976..db9651fb 100644 --- a/Cesium.CodeGen/Ir/Types/CTypeSystem.cs +++ b/Cesium.CodeGen/Ir/Types/CTypeSystem.cs @@ -1,6 +1,5 @@ using Cesium.CodeGen.Extensions; using Cesium.Core; -using Mono.Cecil; namespace Cesium.CodeGen.Ir.Types; @@ -22,17 +21,6 @@ internal class CTypeSystem public IType NativeInt { get; } = new PrimitiveType(PrimitiveTypeKind.NativeInt); public IType NativeUInt { get; } = new PrimitiveType(PrimitiveTypeKind.NativeUInt); - public IType RuntimeVoidPtr { get; } - - public CTypeSystem(AssemblyDefinition cesiumRuntimeAssembly) - { - TypeReference GetRuntimeType(string name) => - cesiumRuntimeAssembly.GetType(name) ?? - throw new AssertException($"Could not find type {name} in the runtime assembly."); - - RuntimeVoidPtr = new ResolvedPointerType(GetRuntimeType("Cesium.Runtime.VoidPtr")); - } - public bool IsConversionAvailable(IType type, IType targetType) { if (type.IsEqualTo(targetType) diff --git a/Cesium.CodeGen/Ir/Types/PointerType.cs b/Cesium.CodeGen/Ir/Types/PointerType.cs index ea8d1136..2f32f636 100644 --- a/Cesium.CodeGen/Ir/Types/PointerType.cs +++ b/Cesium.CodeGen/Ir/Types/PointerType.cs @@ -30,9 +30,9 @@ public TypeReference ResolveForTypeMember(TranslationUnitContext context) => { TargetArchitectureSet.Wide => Base switch { - FunctionType => context.TypeSystem.RuntimeFPtr(Base.ResolveForTypeMember(context)), - PrimitiveType { Kind: PrimitiveTypeKind.Void } => context.TypeSystem.RuntimeVoidPtr, - _ => context.TypeSystem.RuntimeCPtr(Base.ResolveForTypeMember(context)), + FunctionType => context.RuntimeFuncPtr(Base.ResolveForTypeMember(context)), + PrimitiveType { Kind: PrimitiveTypeKind.Void } => context.RuntimeVoidPtr, + _ => context.RuntimeCPtr(Base.ResolveForTypeMember(context)), }, _ => Resolve(context) }; diff --git a/Cesium.CodeGen/Ir/Types/ResolvedPointerType.cs b/Cesium.CodeGen/Ir/Types/ResolvedPointerType.cs deleted file mode 100644 index 0f371581..00000000 --- a/Cesium.CodeGen/Ir/Types/ResolvedPointerType.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Cesium.CodeGen.Contexts; -using Mono.Cecil; - -namespace Cesium.CodeGen.Ir.Types; - -internal record ResolvedPointerType(TypeReference ResolvedType) : IType -{ - public TypeReference Resolve(TranslationUnitContext context) => ResolvedType; - public int? GetSizeInBytes(TargetArchitectureSet arch) => PointerType.SizeInBytes(arch); -} From e6b682d9aa87603e4064f183423c8ee05f18f926 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 7 Sep 2023 00:24:30 +0200 Subject: [PATCH 25/53] (#354) CodeGen: add a test for CPtr> --- .../ArchitectureDependentTypeTests.cs | 22 ++++++++++++++----- ...ithDoublePointer_arch=Dynamic.verified.txt | 6 +++++ ...ctWithDoublePointer_arch=Wide.verified.txt | 6 +++++ 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithDoublePointer_arch=Dynamic.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithDoublePointer_arch=Wide.verified.txt diff --git a/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs b/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs index fd07c5a9..30a2d709 100644 --- a/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs +++ b/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; namespace Cesium.CodeGen.Tests; @@ -5,13 +6,15 @@ namespace Cesium.CodeGen.Tests; public class ArchitectureDependentTypeTests : CodeGenTestBase { [MustUseReturnValue] - private static Task DoTest(TargetArchitectureSet arch, string source, string @namespace = "", string globalTypeFqn = "") + private static Task DoTest( + TargetArchitectureSet arch, + [StringSyntax("cpp")] string source) { var assembly = GenerateAssembly( default, arch, - @namespace: @namespace, - globalTypeFqn: globalTypeFqn, + @namespace: "", + globalTypeFqn: "", sources: source); return VerifyTypes(assembly, arch); } @@ -39,6 +42,16 @@ typedef struct } foo; """); + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Wide)] + public Task StructWithDoublePointer(TargetArchitectureSet arch) => DoTest(arch, """ + typedef struct + { + int **x; + } foo; + """); + [Fact(DisplayName = "Struct with a fixed array of a pointer type isn't supported for dynamic architecture")] public void StructWithPointerArrayDynamic() => DoesNotCompile(""" typedef struct @@ -47,5 +60,4 @@ typedef struct } foo; """, "Cannot statically determine a size of type", - arch: TargetArchitectureSet.Dynamic); -} + arch: TargetArchitectureSet.Dynamic);} diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithDoublePointer_arch=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithDoublePointer_arch=Dynamic.verified.txt new file mode 100644 index 00000000..7aac3903 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithDoublePointer_arch=Dynamic.verified.txt @@ -0,0 +1,6 @@ +Module: Primary + Type: + + Type: foo + Fields: + System.Int32** foo::x diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithDoublePointer_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithDoublePointer_arch=Wide.verified.txt new file mode 100644 index 00000000..e2dbcfaa --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentTypeTests.StructWithDoublePointer_arch=Wide.verified.txt @@ -0,0 +1,6 @@ +Module: Primary + Type: + + Type: foo + Fields: + Cesium.Runtime.CPtr`1> foo::x From 8667d7d6b87bd1bbf748a8303e2562f148e0d243 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Mon, 30 Oct 2023 23:02:20 +0100 Subject: [PATCH 26/53] (#354) More tests for pointer interop --- .../ArchitectureDependentTypeTests.cs | 23 ++++++++++++++++++- .../CodeGenNetInteropTests.cs | 8 +++---- Cesium.CodeGen.Tests/CodeGenTestBase.cs | 3 ++- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs b/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs index 30a2d709..3dd226e6 100644 --- a/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs +++ b/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs @@ -60,4 +60,25 @@ typedef struct } foo; """, "Cannot statically determine a size of type", - arch: TargetArchitectureSet.Dynamic);} + arch: TargetArchitectureSet.Dynamic); + + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Wide)] + public Task StructWithFuncPtr(TargetArchitectureSet arch) => DoTest(arch, """ +typedef struct +{ + int (*func)(int); +} foo; +"""); + + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Wide)] + public Task StructWithNestedFuncPtr(TargetArchitectureSet arch) => DoTest(arch, """ +typedef struct +{ + int (*func)(int(*)(void)); +} foo; +"""); +} diff --git a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs index b7ec69c9..10b6d93e 100644 --- a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs @@ -69,16 +69,16 @@ int main(void) [Theory] [InlineData(TargetArchitectureSet.Dynamic)] [InlineData(TargetArchitectureSet.Wide)] - public Task VPtrInterop(TargetArchitectureSet architecture) => DoTest( + public Task VoidPtrInterop(TargetArchitectureSet architecture) => DoTest( architecture, @"using Cesium.Runtime; public static class Test { - public static int Func(CPtr ptr) => 1; + public static int Func(VoidPtr ptr) => 1; } ", """ __cli_import("Test::Func") - int Func(int *ptr); + int Func(void *ptr); int main(void) { @@ -90,7 +90,7 @@ int main(void) [Theory] [InlineData(TargetArchitectureSet.Dynamic)] [InlineData(TargetArchitectureSet.Wide)] - public Task FPtrInterop(TargetArchitectureSet architecture) => DoTest( + public Task FuncPtrInterop(TargetArchitectureSet architecture) => DoTest( architecture, @"using Cesium.Runtime; public static class Test diff --git a/Cesium.CodeGen.Tests/CodeGenTestBase.cs b/Cesium.CodeGen.Tests/CodeGenTestBase.cs index 90163f26..4fe50e26 100644 --- a/Cesium.CodeGen.Tests/CodeGenTestBase.cs +++ b/Cesium.CodeGen.Tests/CodeGenTestBase.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Text; using Cesium.CodeGen.Contexts; using Cesium.Core; @@ -37,7 +38,7 @@ protected static AssemblyDefinition GenerateAssembly( } protected static void DoesNotCompile( - string source, + [StringSyntax("cpp")] string source, string expectedMessage, TargetRuntimeDescriptor? runtime = null, TargetArchitectureSet arch = TargetArchitectureSet.Dynamic, From 3f530d651546cab7ce6966c017bfdafc864bb910 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Mon, 30 Oct 2023 23:29:22 +0100 Subject: [PATCH 27/53] (#354) Fix compilation, deduplicate some redundant tests --- .../ArchitectureDependentCodeTests.cs | 19 +++++++++++++++--- .../ArchitectureDependentTypeTests.cs | 20 ------------------- Cesium.Runtime/StdIoFunctions.cs | 2 ++ 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs b/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs index 18a92ee9..ff109233 100644 --- a/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs +++ b/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; namespace Cesium.CodeGen.Tests; @@ -5,7 +6,7 @@ namespace Cesium.CodeGen.Tests; public class ArchitectureDependentCodeTests : CodeGenTestBase { [MustUseReturnValue] - private static Task DoTest(TargetArchitectureSet arch, string source) + private static Task DoTest(TargetArchitectureSet arch, [StringSyntax("cpp")] string source) { var assembly = GenerateAssembly(runtime: default, arch: arch, sources: source); return VerifyTypes(assembly, arch); @@ -93,8 +94,6 @@ public Task FunctionPointerParameter(TargetArchitectureSet arch) => DoTest(arch, int foo(func x) { return 0; } int v_foo(v_func x) { return 0; } """); - // TODO: empty-paren-func ptr - // TODO: vararg-func ptr [Theory] [InlineData(TargetArchitectureSet.Dynamic)] @@ -107,5 +106,19 @@ struct Foo { func x; }; +"""); + // TODO: empty-paren-func ptr + // TODO: vararg-func ptr + + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Bit64)] + [InlineData(TargetArchitectureSet.Bit32)] + [InlineData(TargetArchitectureSet.Wide)] + public Task StructWithNestedFuncPtr(TargetArchitectureSet arch) => DoTest(arch, """ +typedef struct +{ + int (*func)(int(*)(void)); +} foo; """); } diff --git a/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs b/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs index 3dd226e6..a4ea22b1 100644 --- a/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs +++ b/Cesium.CodeGen.Tests/ArchitectureDependentTypeTests.cs @@ -61,24 +61,4 @@ typedef struct """, "Cannot statically determine a size of type", arch: TargetArchitectureSet.Dynamic); - - [Theory] - [InlineData(TargetArchitectureSet.Dynamic)] - [InlineData(TargetArchitectureSet.Wide)] - public Task StructWithFuncPtr(TargetArchitectureSet arch) => DoTest(arch, """ -typedef struct -{ - int (*func)(int); -} foo; -"""); - - [Theory] - [InlineData(TargetArchitectureSet.Dynamic)] - [InlineData(TargetArchitectureSet.Wide)] - public Task StructWithNestedFuncPtr(TargetArchitectureSet arch) => DoTest(arch, """ -typedef struct -{ - int (*func)(int(*)(void)); -} foo; -"""); } diff --git a/Cesium.Runtime/StdIoFunctions.cs b/Cesium.Runtime/StdIoFunctions.cs index cb2e515a..44bb1328 100644 --- a/Cesium.Runtime/StdIoFunctions.cs +++ b/Cesium.Runtime/StdIoFunctions.cs @@ -1,5 +1,7 @@ #if NETSTANDARD using System.Text; +#else +using System.Runtime.InteropServices; #endif namespace Cesium.Runtime; From d94ea64a623cb6453668141cf6cc22c22270f552 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Wed, 1 Nov 2023 00:27:57 +0100 Subject: [PATCH 28/53] (#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)), }, From 19b4a1e34de92a7234b84d8afc76efe9db898997 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Wed, 1 Nov 2023 00:38:02 +0100 Subject: [PATCH 29/53] (#354) CodeGen: fix the remaining tests --- Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs | 6 ++++-- ...ureDependentCodeTests.StructArray_arch=Wide.verified.txt | 2 +- ...odeTests.StructWithNestedFuncPtr_arch=Bit32.verified.txt | 6 ++++++ ...odeTests.StructWithNestedFuncPtr_arch=Bit64.verified.txt | 6 ++++++ ...eTests.StructWithNestedFuncPtr_arch=Dynamic.verified.txt | 6 ++++++ ...CodeTests.StructWithNestedFuncPtr_arch=Wide.verified.txt | 6 ++++++ 6 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Bit32.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Bit64.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Dynamic.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Wide.verified.txt diff --git a/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs b/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs index ff109233..f6b66a99 100644 --- a/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs +++ b/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs @@ -116,9 +116,11 @@ struct Foo [InlineData(TargetArchitectureSet.Bit32)] [InlineData(TargetArchitectureSet.Wide)] public Task StructWithNestedFuncPtr(TargetArchitectureSet arch) => DoTest(arch, """ +typedef int (*func)(void); +typedef int (*hostFunc)(func); typedef struct { - int (*func)(int(*)(void)); + hostFunc foo; } foo; -"""); +"""); // TODO: Check if the resulting field signature works on CLR at all } diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructArray_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructArray_arch=Wide.verified.txt index a9c99e26..2bb8b393 100644 --- a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructArray_arch=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructArray_arch=Wide.verified.txt @@ -23,4 +23,4 @@ Type: foo Fields: - System.Byte* foo::ptr + Cesium.Runtime.CPtr`1 foo::ptr diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Bit32.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Bit32.verified.txt new file mode 100644 index 00000000..9e63c517 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Bit32.verified.txt @@ -0,0 +1,6 @@ +Module: Primary + Type: + + Type: foo + Fields: + method System.Int32 *(method System.Int32 *()) foo::foo diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Bit64.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Bit64.verified.txt new file mode 100644 index 00000000..9e63c517 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Bit64.verified.txt @@ -0,0 +1,6 @@ +Module: Primary + Type: + + Type: foo + Fields: + method System.Int32 *(method System.Int32 *()) foo::foo diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Dynamic.verified.txt new file mode 100644 index 00000000..9e63c517 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Dynamic.verified.txt @@ -0,0 +1,6 @@ +Module: Primary + Type: + + Type: foo + Fields: + method System.Int32 *(method System.Int32 *()) foo::foo diff --git a/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Wide.verified.txt new file mode 100644 index 00000000..bd176d7d --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/ArchitectureDependentCodeTests.StructWithNestedFuncPtr_arch=Wide.verified.txt @@ -0,0 +1,6 @@ +Module: Primary + Type: + + Type: foo + Fields: + Cesium.Runtime.FuncPtr`1> foo::foo From 2d4c68814919aa5a136a0a3aab628775b9390f44 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Wed, 1 Nov 2023 00:46:43 +0100 Subject: [PATCH 30/53] (#354) CodeGen: more tests for the various types of pointer interop --- .../CodeGenNetInteropTests.cs | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs index 10b6d93e..84ed2744 100644 --- a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs @@ -98,17 +98,42 @@ public static class Test public static int Func(FPtr> ptr) => 1; } ", """ - __cli_import("Test::Func") - int Func(int (*ptr)()); +__cli_import("Test::Func") +int Func(int (*ptr)()); - int myFunc() - { - return 0; - } +int myFunc() +{ + return 0; +} - int main(void) - { - return Func(&myFunc); - } - """); +int main(void) +{ + return Func(&myFunc); +} +"""); + + [Theory] + [InlineData(TargetArchitectureSet.Dynamic)] + [InlineData(TargetArchitectureSet.Wide)] + public Task FunctionPointerInterop(TargetArchitectureSet architecture) => DoTest( + architecture, + """ +public static class Test +{ + public static int Func(delegate* ptr) => 1; +} +""", """ +__cli_import("Test::Func") +int Func(int (*ptr)()); + +int myFunc() +{ + return 0; +} + +int main(void) +{ + return Func(&myFunc); +} +"""); } From 84e53f9ee5516429b7eb42c2e05e3cd9800c83b5 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 2 Nov 2023 23:35:38 +0100 Subject: [PATCH 31/53] (#354) CodeGen: match compatibility ptr types with the actual pointers in method lookup --- ...rInterop_architecture=Dynamic.verified.txt | 22 ++++++++++ .../Contexts/TranslationUnitContext.cs | 10 ++--- Cesium.CodeGen/Extensions/TypeSystemEx.cs | 43 ++++++++++++++++++- 3 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Dynamic.verified.txt diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Dynamic.verified.txt new file mode 100644 index 00000000..e824a808 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Dynamic.verified.txt @@ -0,0 +1,22 @@ +Module: Primary + Type: + Methods: + System.Int32 ::main() + Locals: + System.Int32 V_0 + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + // TODO: There should be type cast or ctor call here + IL_0004: call System.Int32 Test::Func(Cesium.Runtime.CPtr`1) + IL_0009: ret + + System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs index 993a0f74..7016644c 100644 --- a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs +++ b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs @@ -14,10 +14,6 @@ namespace Cesium.CodeGen.Contexts; public class TranslationUnitContext { - private const string _cPtrFullTypeName = "Cesium.Runtime.CPtr`1"; - private const string _voidPtrFullTypeName = "Cesium.Runtime.VoidPtr"; - private const string _funcPtrFullTypeName = "Cesium.Runtime.FuncPtr`1"; - public AssemblyContext AssemblyContext { get; } public string Name { get; } @@ -47,9 +43,9 @@ TypeReference GetRuntimeType(string typeName) => assemblyContext.CesiumRuntimeAssembly.GetType(typeName) ?? throw new AssertException($"Could not find type {typeName} in the runtime assembly."); - _runtimeCPtr = Module.ImportReference(GetRuntimeType(_cPtrFullTypeName)); - RuntimeVoidPtr = Module.ImportReference(GetRuntimeType(_voidPtrFullTypeName)); - _runtimeFuncPtr = Module.ImportReference(GetRuntimeType(_funcPtrFullTypeName)); + _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); diff --git a/Cesium.CodeGen/Extensions/TypeSystemEx.cs b/Cesium.CodeGen/Extensions/TypeSystemEx.cs index 07a805d8..c77bee64 100644 --- a/Cesium.CodeGen/Extensions/TypeSystemEx.cs +++ b/Cesium.CodeGen/Extensions/TypeSystemEx.cs @@ -9,6 +9,10 @@ namespace Cesium.CodeGen.Extensions; internal static class TypeSystemEx { + public const string CPtrFullTypeName = "Cesium.Runtime.CPtr`1"; + public const string VoidPtrFullTypeName = "Cesium.Runtime.VoidPtr"; + public const string FuncPtrFullTypeName = "Cesium.Runtime.FuncPtr`1"; + public static MethodReference MethodLookup( this TranslationUnitContext context, string memberName, @@ -85,7 +89,7 @@ private static bool Match( } var declReturnReified = returnType.Resolve(context); - if (declReturnReified.FullName != method.ReturnType.FullName) + if (!TypesCorrespond(declReturnReified, method.ReturnType)) { similarMethods.Add((method, $"Returns types do not match: {declReturnReified.Name} in declaration, {method.ReturnType.Name} in source.")); return false; @@ -99,7 +103,7 @@ private static bool Match( var srcParam = methodParameters[i]; var srcParamType = srcParam.ParameterType; - if (declParamType.FullName != srcParamType.FullName) + if (!TypesCorrespond(declParamType, srcParamType)) { similarMethods.Add((method, $"Type of argument #{i} does not match: {declParamType} in declaration, {srcParamType} in source.")); return false; @@ -132,6 +136,41 @@ private static bool Match( return true; } + /// Determines whether the types correspond to each other. + /// + /// 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. + /// + private static bool TypesCorrespond(TypeReference type1, TypeReference type2) + { + // let type 1 to be pointer out of these two + if (type2.IsPointer || type2.IsFunctionPointer) (type1, type2) = (type2, type1); + var isType1AnyPointer = type1.IsPointer || type1.IsFunctionPointer; + var isType2AnyPointer = type2.IsPointer || type2.IsFunctionPointer; + if (!isType1AnyPointer || isType2AnyPointer) + { + // If type1 is not a pointer, then we don't need to use the compatibility model for this type pair. + // If type2 is a pointer, then type1 is also a pointer, and so no compatibility is required as well. + return type1.FullName == type2.FullName; + } + + if (!type2.IsGenericInstance) return false; + type2 = type2.GetElementType(); + if (type1.IsPointer) + { + // TODO: Analyze the generic argument. + return type2.FullName == CPtrFullTypeName || type2.FullName == VoidPtrFullTypeName; + } + + if (type1.IsFunctionPointer) + { + // TODO: Analyze the generic argument. + return type2.FullName == FuncPtrFullTypeName; + } + + throw new AssertException("Impossible: type1 should be either a pointer or a function pointer."); + } + private static string SimilarMethodsMessage(string name, List<(MethodDefinition, string)> similarMethods) { return $"Cannot find an appropriate overload for CLI-imported function {name}. Candidates:\n" From 8e894e07ec88ac17030d4e6919cb0c7645577ee6 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 2 Nov 2023 23:56:22 +0100 Subject: [PATCH 32/53] (#354) FunctionCallExpression: extract the type conversion into a separate method --- .../Ir/Expressions/FunctionCallExpression.cs | 70 +++++++------------ 1 file changed, 26 insertions(+), 44 deletions(-) diff --git a/Cesium.CodeGen/Ir/Expressions/FunctionCallExpression.cs b/Cesium.CodeGen/Ir/Expressions/FunctionCallExpression.cs index 0e6f43fb..22ed80f0 100644 --- a/Cesium.CodeGen/Ir/Expressions/FunctionCallExpression.cs +++ b/Cesium.CodeGen/Ir/Expressions/FunctionCallExpression.cs @@ -71,30 +71,7 @@ public override IExpression Lower(IDeclarationScope scope) return new IndirectFunctionCallExpression( new GetValueExpression(new LValueLocalVariable(var.Type, var.Identifier)), f, - _arguments.Select((a, index) => - { - int firstVarArgArgument = 0; - if (f.Parameters?.IsVarArg == true) - { - firstVarArgArgument = f.Parameters.Parameters.Count; - } - - if (index >= firstVarArgArgument) - { - var expressionType = a.GetExpressionType(scope); - if (expressionType.Equals(scope.CTypeSystem.Float)) - { - // Seems to be float always use float-point registers and as such we need to covert to double. - return new TypeCastExpression(scope.CTypeSystem.Double, a.Lower(scope)); - } - else - { - return a.Lower(scope); - } - } - - return a.Lower(scope); - }).ToList()); + ConvertArgs(scope, f.Parameters)); } var callee = scope.GetFunctionInfo(functionName); @@ -103,34 +80,39 @@ public override IExpression Lower(IDeclarationScope scope) throw new CompilationException($"Function \"{functionName}\" was not found."); } + return new FunctionCallExpression( + _function, + callee, + ConvertArgs(scope, callee.Parameters)); + } + + private List ConvertArgs(IDeclarationScope scope, ParametersInfo? parameters) + { int firstVarArgArgument = 0; - if (callee.Parameters?.IsVarArg == true) + if (parameters?.IsVarArg == true) { - firstVarArgArgument = callee.Parameters.Parameters.Count; + firstVarArgArgument = parameters.Parameters.Count; } - return new FunctionCallExpression( - _function, - callee, - _arguments.Select((a, index) => + return _arguments.Select((a, index) => + { + if (index >= firstVarArgArgument) { - if (index >= firstVarArgArgument) + var loweredArg = a.Lower(scope); + var expressionType = loweredArg.GetExpressionType(scope); + if (expressionType.Equals(scope.CTypeSystem.Float)) { - var loweredArg = a.Lower(scope); - var expressionType = loweredArg.GetExpressionType(scope); - if (expressionType.Equals(scope.CTypeSystem.Float)) - { - // Seems to be float always use float-point registers and as such we need to covert to double. - return new TypeCastExpression(scope.CTypeSystem.Double, loweredArg); - } - else - { - return loweredArg; - } + // Seems to be float always use float-point registers and as such we need to covert to double. + return new TypeCastExpression(scope.CTypeSystem.Double, loweredArg); } + else + { + return loweredArg; + } + } - return a.Lower(scope); - }).ToList()); + return a.Lower(scope); + }).ToList(); } public override void EmitTo(IEmitScope scope) From 2e93313025a37b03dfa2ba8939db19c1b5448f88 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Fri, 3 Nov 2023 00:11:38 +0100 Subject: [PATCH 33/53] (#354) CodeGen: groundwork for CLR type conversion --- .../Ir/Expressions/FunctionCallExpression.cs | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/Cesium.CodeGen/Ir/Expressions/FunctionCallExpression.cs b/Cesium.CodeGen/Ir/Expressions/FunctionCallExpression.cs index 22ed80f0..99f2ae2f 100644 --- a/Cesium.CodeGen/Ir/Expressions/FunctionCallExpression.cs +++ b/Cesium.CodeGen/Ir/Expressions/FunctionCallExpression.cs @@ -80,6 +80,9 @@ 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, @@ -96,25 +99,38 @@ private List ConvertArgs(IDeclarationScope scope, ParametersInfo? p return _arguments.Select((a, index) => { - if (index >= firstVarArgArgument) + IType targetType; + var loweredArg = a.Lower(scope); + if (index < firstVarArgArgument) { - var loweredArg = a.Lower(scope); - var expressionType = loweredArg.GetExpressionType(scope); - if (expressionType.Equals(scope.CTypeSystem.Float)) - { - // Seems to be float always use float-point registers and as such we need to covert to double. - return new TypeCastExpression(scope.CTypeSystem.Double, loweredArg); - } - else + // Argument is not in vararg argument list. Just use the declared type. + targetType = parameters!.Parameters[index].Type; + } + else + { + // Argument is in a vararg list. Use the actual argument type, except for cases when it is float + // (convert to double then). + targetType = loweredArg.GetExpressionType(scope); + if (targetType.Equals(scope.CTypeSystem.Float)) { - return loweredArg; + targetType = scope.CTypeSystem.Double; } } - return a.Lower(scope); + return CastTypeIfRequired(scope, loweredArg, targetType); }).ToList(); } + private static IExpression CastTypeIfRequired(IDeclarationScope scope, IExpression expression, IType targetType) + { + if (expression.GetExpressionType(scope).IsEqualTo(targetType)) + { + return expression; + } + + return new TypeCastExpression(targetType, expression); + } + public override void EmitTo(IEmitScope scope) { if (_callee == null) From fc71a17f6ce0b13e5b230ba82fc6005f2f99ac28 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 5 Nov 2023 21:18:03 +0100 Subject: [PATCH 34/53] (#354) CodeGen: call implicit cast operator for VoidPtr --- .../CodeGenNetInteropTests.cs | 2 +- ...CPtrInterop_architecture=Wide.verified.txt | 0 ...rInterop_architecture=Dynamic.verified.txt | 22 ++++ ...dPtrInterop_architecture=Wide.verified.txt | 22 ++++ Cesium.CodeGen/Contexts/AssemblyContext.cs | 62 +++++++++++ Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs | 4 +- .../Contexts/TranslationUnitContext.cs | 105 +++++++++--------- .../Utilities/GenericDelegateTypeCache.cs | 5 +- Cesium.CodeGen/Extensions/TypeSystemEx.cs | 14 ++- .../Ir/Expressions/FunctionCallExpression.cs | 10 +- .../Ir/Expressions/TypeCastExpression.cs | 9 ++ Cesium.CodeGen/Ir/Types/FunctionType.cs | 2 +- Cesium.CodeGen/Ir/Types/InteropType.cs | 50 +++++++++ Cesium.CodeGen/Ir/Types/PointerType.cs | 6 +- 14 files changed, 235 insertions(+), 78 deletions(-) create mode 100644 Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Dynamic.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Wide.verified.txt create mode 100644 Cesium.CodeGen/Ir/Types/InteropType.cs diff --git a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs index 84ed2744..be2efc0c 100644 --- a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs @@ -74,7 +74,7 @@ public Task VoidPtrInterop(TargetArchitectureSet architecture) => DoTest( @"using Cesium.Runtime; public static class Test { - public static int Func(VoidPtr ptr) => 1; + public static int Func(VoidPtr ptr) => 1; } ", """ __cli_import("Test::Func") diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt new file mode 100644 index 00000000..e69de29b diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Dynamic.verified.txt new file mode 100644 index 00000000..c57a332d --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Dynamic.verified.txt @@ -0,0 +1,22 @@ +Module: Primary + Type: + Methods: + System.Int32 ::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 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Wide.verified.txt new file mode 100644 index 00000000..c57a332d --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Wide.verified.txt @@ -0,0 +1,22 @@ +Module: Primary + Type: + Methods: + System.Int32 ::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 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen/Contexts/AssemblyContext.cs b/Cesium.CodeGen/Contexts/AssemblyContext.cs index a62ce9a7..e3883c39 100644 --- a/Cesium.CodeGen/Contexts/AssemblyContext.cs +++ b/Cesium.CodeGen/Contexts/AssemblyContext.cs @@ -3,6 +3,7 @@ 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; @@ -10,6 +11,7 @@ using Cesium.Core; using Mono.Cecil; using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; namespace Cesium.CodeGen.Contexts; @@ -74,6 +76,13 @@ public AssemblyDefinition VerifyAndGetAssembly() private readonly Lazy _constantPool; private MethodDefinition? _globalInitializer; + private readonly TypeReference _runtimeCPtr; + public TypeReference RuntimeVoidPtr { get; } + private readonly TypeReference _runtimeFuncPtr; + + private readonly Lazy _voidPtrConverter; + public MethodReference VoidPtrConverter => _voidPtrConverter.Value; + private AssemblyContext( AssemblyDefinition assembly, ModuleDefinition module, @@ -115,6 +124,59 @@ private AssemblyContext( { GlobalType = Module.GetType(""); } + + 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; + + /// + /// Resolves a standard delegate type (i.e. an or a ), depending on + /// the return type. + /// + public TypeReference StandardDelegateType(TypeReference returnType, IEnumerable 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) diff --git a/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs b/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs index 21046ccd..cae6723e 100644 --- a/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs +++ b/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs @@ -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) diff --git a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs index 7016644c..7a00bbbb 100644 --- a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs +++ b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs @@ -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; @@ -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); } - /// - /// Resolves a standard delegate type (i.e. an or a ), depending on - /// the return type. - /// - public TypeReference StandardDelegateType(TypeReference returnType, IEnumerable 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; - /// /// 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. @@ -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 { @@ -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 ProcessParameters(ICollection 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; + } + } } diff --git a/Cesium.CodeGen/Contexts/Utilities/GenericDelegateTypeCache.cs b/Cesium.CodeGen/Contexts/Utilities/GenericDelegateTypeCache.cs index 6d623ac6..f9dc4344 100644 --- a/Cesium.CodeGen/Contexts/Utilities/GenericDelegateTypeCache.cs +++ b/Cesium.CodeGen/Contexts/Utilities/GenericDelegateTypeCache.cs @@ -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 _cache = new(); @@ -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)); diff --git a/Cesium.CodeGen/Extensions/TypeSystemEx.cs b/Cesium.CodeGen/Extensions/TypeSystemEx.cs index c77bee64..5eb1dff5 100644 --- a/Cesium.CodeGen/Extensions/TypeSystemEx.cs +++ b/Cesium.CodeGen/Extensions/TypeSystemEx.cs @@ -4,6 +4,7 @@ using Cesium.CodeGen.Ir.Types; using Cesium.Core; using Mono.Cecil; +using PointerType = Mono.Cecil.PointerType; namespace Cesium.CodeGen.Extensions; @@ -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; @@ -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; @@ -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. /// - 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); @@ -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) diff --git a/Cesium.CodeGen/Ir/Expressions/FunctionCallExpression.cs b/Cesium.CodeGen/Ir/Expressions/FunctionCallExpression.cs index 99f2ae2f..98b22886 100644 --- a/Cesium.CodeGen/Ir/Expressions/FunctionCallExpression.cs +++ b/Cesium.CodeGen/Ir/Expressions/FunctionCallExpression.cs @@ -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, @@ -91,12 +88,7 @@ public override IExpression Lower(IDeclarationScope scope) private List 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; diff --git a/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs b/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs index e6daa023..0c3ddbf0 100644 --- a/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs +++ b/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs @@ -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."); diff --git a/Cesium.CodeGen/Ir/Types/FunctionType.cs b/Cesium.CodeGen/Ir/Types/FunctionType.cs index 02c2a945..9f488d2e 100644 --- a/Cesium.CodeGen/Ir/Types/FunctionType.cs +++ b/Cesium.CodeGen/Ir/Types/FunctionType.cs @@ -29,7 +29,7 @@ public TypeReference ResolveAsDelegateType(TranslationUnitContext context) 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); + return context.AssemblyContext.StandardDelegateType(returnType, arguments); } public TypeReference ResolvePointer(TranslationUnitContext context) diff --git a/Cesium.CodeGen/Ir/Types/InteropType.cs b/Cesium.CodeGen/Ir/Types/InteropType.cs new file mode 100644 index 00000000..40cd2a92 --- /dev/null +++ b/Cesium.CodeGen/Ir/Types/InteropType.cs @@ -0,0 +1,50 @@ +using Cesium.CodeGen.Contexts; +using Cesium.CodeGen.Extensions; +using Cesium.Core; +using Mono.Cecil; + +namespace Cesium.CodeGen.Ir.Types; + +/// +/// Type that was imported from CLI for Cesium/CLI interop, most likely via __cli_import. +/// +internal record InteropType(TypeReference UnderlyingType) : IType +{ + public TypeReference Resolve(TranslationUnitContext context) => UnderlyingType; + + public int? GetSizeInBytes(TargetArchitectureSet arch) + { + switch (UnderlyingType) + { + case { FullName: TypeSystemEx.VoidPtrFullTypeName }: + return PointerType.SizeInBytes(arch); + case { IsGenericInstance: true }: + { + var parent = UnderlyingType.GetElementType(); + + if (parent.FullName is TypeSystemEx.CPtrFullTypeName or TypeSystemEx.FuncPtrFullTypeName) + return PointerType.SizeInBytes(arch); + break; + } + } + + throw new AssertException( + $"{nameof(InteropType)} doesn't know how to get size of an underlying {UnderlyingType}."); + } + + public MethodReference GetConvertCall(AssemblyContext context) + { + if (UnderlyingType.FullName == TypeSystemEx.VoidPtrFullTypeName) + return context.VoidPtrConverter; + + if (UnderlyingType.IsGenericInstance) + { + var parent = UnderlyingType.GetElementType(); + + throw new WipException(WipException.ToDo, "Cannot import converter methods for CPtr or FuncPtr, yet."); + } + + throw new AssertException( + $"{nameof(InteropType)} doesn't know how to get a converter call for an underlying {UnderlyingType}."); + } +} diff --git a/Cesium.CodeGen/Ir/Types/PointerType.cs b/Cesium.CodeGen/Ir/Types/PointerType.cs index 6dccb08d..4f1ff999 100644 --- a/Cesium.CodeGen/Ir/Types/PointerType.cs +++ b/Cesium.CodeGen/Ir/Types/PointerType.cs @@ -30,9 +30,9 @@ public TypeReference ResolveForTypeMember(TranslationUnitContext context) => { TargetArchitectureSet.Wide => Base switch { - FunctionType rawFunc => context.RuntimeFuncPtr(rawFunc.ResolveAsDelegateType(context)), - PrimitiveType { Kind: PrimitiveTypeKind.Void } => context.RuntimeVoidPtr, - _ => context.RuntimeCPtr(Base.ResolveForTypeMember(context)), + FunctionType rawFunc => context.AssemblyContext.RuntimeFuncPtr(rawFunc.ResolveAsDelegateType(context)), + PrimitiveType { Kind: PrimitiveTypeKind.Void } => context.AssemblyContext.RuntimeVoidPtr, + _ => context.AssemblyContext.RuntimeCPtr(Base.ResolveForTypeMember(context)), }, _ => Resolve(context) }; From ee109a1bba253e9a3d1cd6aaf1380dc8ae53ee29 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 5 Nov 2023 21:23:54 +0100 Subject: [PATCH 35/53] (#354) CodeGen: emit additional type case before calling the cast operator --- ...pTests.VoidPtrInterop_architecture=Dynamic.verified.txt | 7 ++++--- ...eropTests.VoidPtrInterop_architecture=Wide.verified.txt | 7 ++++--- Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs | 1 + 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Dynamic.verified.txt index c57a332d..38931ba8 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Dynamic.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Dynamic.verified.txt @@ -7,9 +7,10 @@ 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 + IL_0004: conv.i + IL_0005: call Cesium.Runtime.VoidPtr Cesium.Runtime.VoidPtr::op_Implicit(System.Void*) + IL_000a: call System.Int32 Test::Func(Cesium.Runtime.VoidPtr) + IL_000f: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Wide.verified.txt index c57a332d..38931ba8 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Wide.verified.txt @@ -7,9 +7,10 @@ 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 + IL_0004: conv.i + IL_0005: call Cesium.Runtime.VoidPtr Cesium.Runtime.VoidPtr::op_Implicit(System.Void*) + IL_000a: call System.Int32 Test::Func(Cesium.Runtime.VoidPtr) + IL_000f: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs b/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs index 0c3ddbf0..2d9bbca6 100644 --- a/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs +++ b/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs @@ -60,6 +60,7 @@ public void EmitTo(IEmitScope scope) { if (iType.UnderlyingType.FullName == TypeSystemEx.VoidPtrFullTypeName) { + Add(OpCodes.Conv_I); // TODO: Should only emit if required. scope.Method.Body.Instructions.Add( Instruction.Create(OpCodes.Call, iType.GetConvertCall(scope.AssemblyContext))); } From e64f5d5482a5caf679a6efd710e4f3321228d248 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 12 Nov 2023 00:45:25 +0100 Subject: [PATCH 36/53] (#354) CodeGen: move further on CPtr cast operator --- ...sts.CPtrInterop_architecture=Wide.verified.txt | 0 Cesium.CodeGen/Contexts/AssemblyContext.cs | 15 +++++++++++---- .../Ir/Expressions/TypeCastExpression.cs | 4 +++- Cesium.CodeGen/Ir/Types/InteropType.cs | 4 +++- 4 files changed, 17 insertions(+), 6 deletions(-) delete mode 100644 Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/Cesium.CodeGen/Contexts/AssemblyContext.cs b/Cesium.CodeGen/Contexts/AssemblyContext.cs index e3883c39..cd880f89 100644 --- a/Cesium.CodeGen/Contexts/AssemblyContext.cs +++ b/Cesium.CodeGen/Contexts/AssemblyContext.cs @@ -77,6 +77,9 @@ public AssemblyDefinition VerifyAndGetAssembly() private MethodDefinition? _globalInitializer; private readonly TypeReference _runtimeCPtr; + private readonly Lazy _cPtrConverter; + public MethodReference CPtrConverter => _cPtrConverter.Value; + public TypeReference RuntimeVoidPtr { get; } private readonly TypeReference _runtimeFuncPtr; @@ -136,11 +139,15 @@ TypeDefinition GetRuntimeType(string typeName) => _importedActionDelegates = new("System", "Action", Module); _importedFuncDelegates = new("System", "Func", Module); - _voidPtrConverter = new(() => + _voidPtrConverter = new(() => GetImplicitCastOperator(TypeSystemEx.VoidPtrFullTypeName)); + _cPtrConverter = new(() => throw new WipException(WipException.ToDo, "TODO: Make the cast operators specialized by types, introduce a cache or something.")); + return; + + MethodReference GetImplicitCastOperator(string typeName) { - var voidPtrType = GetRuntimeType(TypeSystemEx.VoidPtrFullTypeName); - return Module.ImportReference(voidPtrType.Methods.Single(m => m.Name == "op_Implicit")); - }); + var type = GetRuntimeType(typeName); + return Module.ImportReference(type.Methods.Single(m => m.Name == "op_Implicit")); + } } public TypeReference RuntimeCPtr(TypeReference typeReference) diff --git a/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs b/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs index 2d9bbca6..2ebcc0c4 100644 --- a/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs +++ b/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs @@ -58,7 +58,9 @@ public void EmitTo(IEmitScope scope) Add(OpCodes.Conv_I); else if (TargetType is InteropType iType) { - if (iType.UnderlyingType.FullName == TypeSystemEx.VoidPtrFullTypeName) + var type = iType.UnderlyingType; + if (type.FullName == TypeSystemEx.VoidPtrFullTypeName + || type.IsGenericInstance && type.GetElementType().FullName == TypeSystemEx.CPtrFullTypeName) { Add(OpCodes.Conv_I); // TODO: Should only emit if required. scope.Method.Body.Instructions.Add( diff --git a/Cesium.CodeGen/Ir/Types/InteropType.cs b/Cesium.CodeGen/Ir/Types/InteropType.cs index 40cd2a92..acf228bf 100644 --- a/Cesium.CodeGen/Ir/Types/InteropType.cs +++ b/Cesium.CodeGen/Ir/Types/InteropType.cs @@ -40,8 +40,10 @@ public MethodReference GetConvertCall(AssemblyContext context) if (UnderlyingType.IsGenericInstance) { var parent = UnderlyingType.GetElementType(); + if (parent.FullName == TypeSystemEx.CPtrFullTypeName) + return context.CPtrConverter; - throw new WipException(WipException.ToDo, "Cannot import converter methods for CPtr or FuncPtr, yet."); + throw new WipException(WipException.ToDo, "Cannot import the converter method for FuncPtr, yet."); } throw new AssertException( From e4c9183242cd5a906c057b13056f16847d99011f Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Tue, 14 Nov 2023 00:36:43 +0100 Subject: [PATCH 37/53] (#354) CodeGen: support proper CPtr interop --- ...rInterop_architecture=Dynamic.verified.txt | 7 +-- ...CPtrInterop_architecture=Wide.verified.txt | 23 +++++++++ Cesium.CodeGen/Contexts/AssemblyContext.cs | 8 +-- .../Contexts/Utilities/GenericTypeCache.cs | 49 +++++++++++++++++++ Cesium.CodeGen/Ir/Types/InteropType.cs | 6 +-- 5 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt create mode 100644 Cesium.CodeGen/Contexts/Utilities/GenericTypeCache.cs diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Dynamic.verified.txt index e824a808..5cc38517 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Dynamic.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Dynamic.verified.txt @@ -7,9 +7,10 @@ IL_0000: ldc.i4.0 IL_0001: stloc.0 IL_0002: ldloca.s V_0 - // TODO: There should be type cast or ctor call here - IL_0004: call System.Int32 Test::Func(Cesium.Runtime.CPtr`1) - IL_0009: ret + IL_0004: conv.i + IL_0005: call Cesium.Runtime.CPtr`1 Cesium.Runtime.CPtr`1::op_Implicit(T*) + IL_000a: call System.Int32 Test::Func(Cesium.Runtime.CPtr`1) + IL_000f: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt new file mode 100644 index 00000000..5cc38517 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt @@ -0,0 +1,23 @@ +Module: Primary + Type: + Methods: + System.Int32 ::main() + Locals: + System.Int32 V_0 + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: conv.i + IL_0005: call Cesium.Runtime.CPtr`1 Cesium.Runtime.CPtr`1::op_Implicit(T*) + IL_000a: call System.Int32 Test::Func(Cesium.Runtime.CPtr`1) + IL_000f: ret + + System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen/Contexts/AssemblyContext.cs b/Cesium.CodeGen/Contexts/AssemblyContext.cs index cd880f89..bf90bc4c 100644 --- a/Cesium.CodeGen/Contexts/AssemblyContext.cs +++ b/Cesium.CodeGen/Contexts/AssemblyContext.cs @@ -77,8 +77,9 @@ public AssemblyDefinition VerifyAndGetAssembly() private MethodDefinition? _globalInitializer; private readonly TypeReference _runtimeCPtr; - private readonly Lazy _cPtrConverter; - public MethodReference CPtrConverter => _cPtrConverter.Value; + private readonly ConversionMethodCache _cPtrConverterCache; + public MethodReference CPtrConverter(TypeReference argument) => + _cPtrConverterCache.GetOrImportMethod(argument); public TypeReference RuntimeVoidPtr { get; } private readonly TypeReference _runtimeFuncPtr; @@ -133,6 +134,7 @@ TypeDefinition GetRuntimeType(string typeName) => throw new AssertException($"Could not find type {typeName} in the runtime assembly."); _runtimeCPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.CPtrFullTypeName)); + _cPtrConverterCache = new ConversionMethodCache(_runtimeCPtr, "op_Implicit", Module); RuntimeVoidPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.VoidPtrFullTypeName)); _runtimeFuncPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.FuncPtrFullTypeName)); @@ -140,8 +142,6 @@ TypeDefinition GetRuntimeType(string typeName) => _importedFuncDelegates = new("System", "Func", Module); _voidPtrConverter = new(() => GetImplicitCastOperator(TypeSystemEx.VoidPtrFullTypeName)); - _cPtrConverter = new(() => throw new WipException(WipException.ToDo, "TODO: Make the cast operators specialized by types, introduce a cache or something.")); - return; MethodReference GetImplicitCastOperator(string typeName) { diff --git a/Cesium.CodeGen/Contexts/Utilities/GenericTypeCache.cs b/Cesium.CodeGen/Contexts/Utilities/GenericTypeCache.cs new file mode 100644 index 00000000..f4b0fa2a --- /dev/null +++ b/Cesium.CodeGen/Contexts/Utilities/GenericTypeCache.cs @@ -0,0 +1,49 @@ +using Mono.Cecil; +using Mono.Cecil.Rocks; + +namespace Cesium.CodeGen.Contexts.Utilities; + +/// +/// Currently specialized for operators that look like this: +/// +/// class {GenericType}<T> +/// { +/// public static {GenericType}<T> {MethodName}({any argument}); +/// } +/// +/// +internal record ConversionMethodCache( + TypeReference GenericType, + string MethodName, + ModuleDefinition TargetModule) +{ + private readonly Dictionary _methods = new(); + private readonly object _lock = new(); + public MethodReference GetOrImportMethod(TypeReference argument) + { + var fqn = argument.FullName; + lock (_lock) + { + if (_methods.GetValueOrDefault(fqn) is { } method) return method; + return _methods[fqn] = FindMethod(argument); + } + } + + private MethodReference FindMethod(TypeReference argument) + { + var genericMethod = GenericType.Resolve().Methods.Single(m => m.Name == MethodName); + var declaringType = GenericType.MakeGenericInstanceType(argument); + var methodReference = new MethodReference( + genericMethod.Name, + returnType: GenericType.MakeGenericInstanceType( + GenericType.GenericParameters.Single()), + declaringType); + foreach (var p in genericMethod.Parameters) + { + methodReference.Parameters.Add( + new ParameterDefinition(p.Name, p.Attributes, p.ParameterType)); + } + + return TargetModule.ImportReference(methodReference); + } +} diff --git a/Cesium.CodeGen/Ir/Types/InteropType.cs b/Cesium.CodeGen/Ir/Types/InteropType.cs index acf228bf..8f1fa972 100644 --- a/Cesium.CodeGen/Ir/Types/InteropType.cs +++ b/Cesium.CodeGen/Ir/Types/InteropType.cs @@ -37,11 +37,11 @@ public MethodReference GetConvertCall(AssemblyContext context) if (UnderlyingType.FullName == TypeSystemEx.VoidPtrFullTypeName) return context.VoidPtrConverter; - if (UnderlyingType.IsGenericInstance) + if (UnderlyingType is GenericInstanceType typeInstance) { - var parent = UnderlyingType.GetElementType(); + var parent = typeInstance.GetElementType(); if (parent.FullName == TypeSystemEx.CPtrFullTypeName) - return context.CPtrConverter; + return context.CPtrConverter(typeInstance.GenericArguments.Single()); throw new WipException(WipException.ToDo, "Cannot import the converter method for FuncPtr, yet."); } From 1a9bf182a200488cdfbbb38e263c0900dfebd9ef Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Fri, 17 Nov 2023 23:00:32 +0100 Subject: [PATCH 38/53] (#354) FuncPtr conversion via constructor --- .../CodeGenNetInteropTests.cs | 2 +- ...rInterop_architecture=Dynamic.verified.txt | 23 +++++++++++++++++++ ...cPtrInterop_architecture=Wide.verified.txt | 23 +++++++++++++++++++ Cesium.CodeGen/Contexts/AssemblyContext.cs | 13 +++++++---- .../Ir/Expressions/TypeCastExpression.cs | 11 ++------- Cesium.CodeGen/Ir/Types/InteropType.cs | 19 ++++++++++----- 6 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Dynamic.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Wide.verified.txt diff --git a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs index be2efc0c..c63c2030 100644 --- a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs @@ -95,7 +95,7 @@ public Task FuncPtrInterop(TargetArchitectureSet architecture) => DoTest( @"using Cesium.Runtime; public static class Test { - public static int Func(FPtr> ptr) => 1; + public static int Func(FuncPtr> ptr) => 1; } ", """ __cli_import("Test::Func") diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Dynamic.verified.txt new file mode 100644 index 00000000..d58cb886 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Dynamic.verified.txt @@ -0,0 +1,23 @@ +Module: Primary + Type: + Methods: + System.Int32 ::myFunc() + IL_0000: ldc.i4.0 + IL_0001: ret + + System.Int32 ::main() + IL_0000: ldftn System.Int32 ::myFunc() + IL_0006: conv.i + IL_0007: newobj Cesium.Runtime.FuncPtr`1 Cesium.Runtime.FuncPtr`1>::.ctor(System.Void*) + IL_000c: call System.Int32 Test::Func(Cesium.Runtime.FuncPtr`1>) + IL_0011: ret + + System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Wide.verified.txt new file mode 100644 index 00000000..d58cb886 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Wide.verified.txt @@ -0,0 +1,23 @@ +Module: Primary + Type: + Methods: + System.Int32 ::myFunc() + IL_0000: ldc.i4.0 + IL_0001: ret + + System.Int32 ::main() + IL_0000: ldftn System.Int32 ::myFunc() + IL_0006: conv.i + IL_0007: newobj Cesium.Runtime.FuncPtr`1 Cesium.Runtime.FuncPtr`1>::.ctor(System.Void*) + IL_000c: call System.Int32 Test::Func(Cesium.Runtime.FuncPtr`1>) + IL_0011: ret + + System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen/Contexts/AssemblyContext.cs b/Cesium.CodeGen/Contexts/AssemblyContext.cs index bf90bc4c..365443c0 100644 --- a/Cesium.CodeGen/Contexts/AssemblyContext.cs +++ b/Cesium.CodeGen/Contexts/AssemblyContext.cs @@ -82,11 +82,14 @@ public MethodReference CPtrConverter(TypeReference argument) => _cPtrConverterCache.GetOrImportMethod(argument); public TypeReference RuntimeVoidPtr { get; } - private readonly TypeReference _runtimeFuncPtr; - private readonly Lazy _voidPtrConverter; public MethodReference VoidPtrConverter => _voidPtrConverter.Value; + private readonly TypeReference _runtimeFuncPtr; + private readonly ConversionMethodCache _funcPtrConstructorCache; + public MethodReference FuncPtrConstructor(TypeReference argument) => + _funcPtrConstructorCache.GetOrImportMethod(argument); + private AssemblyContext( AssemblyDefinition assembly, ModuleDefinition module, @@ -135,14 +138,16 @@ TypeDefinition GetRuntimeType(string typeName) => _runtimeCPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.CPtrFullTypeName)); _cPtrConverterCache = new ConversionMethodCache(_runtimeCPtr, "op_Implicit", Module); + RuntimeVoidPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.VoidPtrFullTypeName)); + _voidPtrConverter = new(() => GetImplicitCastOperator(TypeSystemEx.VoidPtrFullTypeName)); + _runtimeFuncPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.FuncPtrFullTypeName)); + _funcPtrConstructorCache = new ConversionMethodCache(_runtimeFuncPtr, ".ctor", Module); _importedActionDelegates = new("System", "Action", Module); _importedFuncDelegates = new("System", "Func", Module); - _voidPtrConverter = new(() => GetImplicitCastOperator(TypeSystemEx.VoidPtrFullTypeName)); - MethodReference GetImplicitCastOperator(string typeName) { var type = GetRuntimeType(typeName); diff --git a/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs b/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs index 2ebcc0c4..6c525b98 100644 --- a/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs +++ b/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs @@ -58,15 +58,8 @@ public void EmitTo(IEmitScope scope) Add(OpCodes.Conv_I); else if (TargetType is InteropType iType) { - var type = iType.UnderlyingType; - if (type.FullName == TypeSystemEx.VoidPtrFullTypeName - || type.IsGenericInstance && type.GetElementType().FullName == TypeSystemEx.CPtrFullTypeName) - { - Add(OpCodes.Conv_I); // TODO: Should only emit if required. - 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."); + Add(OpCodes.Conv_I); // TODO: Should only emit if required. + scope.Method.Body.Instructions.Add(iType.GetConvertInstruction(scope.AssemblyContext)); } else throw new AssertException($"Type {TargetType} is not supported."); diff --git a/Cesium.CodeGen/Ir/Types/InteropType.cs b/Cesium.CodeGen/Ir/Types/InteropType.cs index 8f1fa972..e758ade2 100644 --- a/Cesium.CodeGen/Ir/Types/InteropType.cs +++ b/Cesium.CodeGen/Ir/Types/InteropType.cs @@ -2,6 +2,7 @@ using Cesium.CodeGen.Extensions; using Cesium.Core; using Mono.Cecil; +using Mono.Cecil.Cil; namespace Cesium.CodeGen.Ir.Types; @@ -32,18 +33,24 @@ internal record InteropType(TypeReference UnderlyingType) : IType $"{nameof(InteropType)} doesn't know how to get size of an underlying {UnderlyingType}."); } - public MethodReference GetConvertCall(AssemblyContext context) + public Instruction GetConvertInstruction(AssemblyContext context) { if (UnderlyingType.FullName == TypeSystemEx.VoidPtrFullTypeName) - return context.VoidPtrConverter; + return Instruction.Create(OpCodes.Call, context.VoidPtrConverter); if (UnderlyingType is GenericInstanceType typeInstance) { var parent = typeInstance.GetElementType(); - if (parent.FullName == TypeSystemEx.CPtrFullTypeName) - return context.CPtrConverter(typeInstance.GenericArguments.Single()); - - throw new WipException(WipException.ToDo, "Cannot import the converter method for FuncPtr, yet."); + return parent.FullName switch + { + TypeSystemEx.CPtrFullTypeName => + Instruction.Create(OpCodes.Call, context.CPtrConverter(typeInstance.GenericArguments.Single())), + TypeSystemEx.FuncPtrFullTypeName => + Instruction.Create( + OpCodes.Newobj, + context.FuncPtrConstructor(typeInstance.GenericArguments.Single())), + _ => throw new AssertException($"No conversion available for interop type {parent}.") + }; } throw new AssertException( From 862f518fc2e46de3685715fb2cda34d3f3fc3670 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sat, 18 Nov 2023 22:04:57 +0100 Subject: [PATCH 39/53] (#354) Tests: fix for unsafe class --- .../CodeGenNetInteropTests.cs | 2 +- ...rInterop_architecture=Dynamic.verified.txt | 21 +++++++++++++++++++ ...nterInterop_architecture=Wide.verified.txt | 21 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Dynamic.verified.txt create mode 100644 Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Wide.verified.txt diff --git a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs index c63c2030..a83f9a39 100644 --- a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs @@ -118,7 +118,7 @@ int main(void) public Task FunctionPointerInterop(TargetArchitectureSet architecture) => DoTest( architecture, """ -public static class Test +public static unsafe class Test { public static int Func(delegate* ptr) => 1; } diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Dynamic.verified.txt new file mode 100644 index 00000000..c824729d --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Dynamic.verified.txt @@ -0,0 +1,21 @@ +Module: Primary + Type: + Methods: + System.Int32 ::myFunc() + IL_0000: ldc.i4.0 + IL_0001: ret + + System.Int32 ::main() + IL_0000: ldftn System.Int32 ::myFunc() + IL_0006: call System.Int32 Test::Func(method System.Int32 *()) + IL_000b: ret + + System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Wide.verified.txt new file mode 100644 index 00000000..c824729d --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Wide.verified.txt @@ -0,0 +1,21 @@ +Module: Primary + Type: + Methods: + System.Int32 ::myFunc() + IL_0000: ldc.i4.0 + IL_0001: ret + + System.Int32 ::main() + IL_0000: ldftn System.Int32 ::myFunc() + IL_0006: call System.Int32 Test::Func(method System.Int32 *()) + IL_000b: ret + + System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::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 From 12b22d48d31f4fb61ea2826dbd57ffe1b3a7e01e Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sat, 18 Nov 2023 22:20:25 +0100 Subject: [PATCH 40/53] (#354) CodeGen: improve validation of vararg CLI imports --- Cesium.CodeGen/Contexts/TranslationUnitContext.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs index 7a00bbbb..bd4389a3 100644 --- a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs +++ b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs @@ -234,11 +234,15 @@ private FunctionInfo ProcessCliImport(FunctionInfo declaration, MethodReference List ProcessParameters(ICollection parameters) { - // For now, only wrap the interop types. - if (implementation.Parameters.Count != parameters.Count) + var areParametersValid = + implementation.Parameters.Count == parameters.Count + || declaration.Parameters.IsVarArg; // TODO: A better check for interop functions + vararg. + if (!areParametersValid) + { 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 => From 3259db0851d8b29d24bc3ce96b1d9e323a68e3bc Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sat, 18 Nov 2023 22:32:33 +0100 Subject: [PATCH 41/53] (#354) Tests: fix the runtimes properly --- Cesium.Test.Framework/CSharpCompilationUtil.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Cesium.Test.Framework/CSharpCompilationUtil.cs b/Cesium.Test.Framework/CSharpCompilationUtil.cs index 958caf63..db20a668 100644 --- a/Cesium.Test.Framework/CSharpCompilationUtil.cs +++ b/Cesium.Test.Framework/CSharpCompilationUtil.cs @@ -10,7 +10,8 @@ public static class CSharpCompilationUtil { public static readonly TargetRuntimeDescriptor DefaultRuntime = TargetRuntimeDescriptor.Net60; private const string _configuration = "Debug"; - private const string _targetRuntime = "net6.0"; + private const string _targetRuntime = "net7.0"; + private const string _cesiumRuntimeLibTargetRuntime = "net6.0"; private const string _projectName = "TestProject"; /// Semaphore that controls the amount of simultaneously running tests. @@ -76,7 +77,7 @@ private static string GetCesiumRuntimeLibraryPath() => Path.Combine( "Cesium.Runtime", "bin", _configuration, - _targetRuntime, + _cesiumRuntimeLibTargetRuntime, "Cesium.Runtime.dll"); private static Task CompileCSharpProject(ITestOutputHelper output, string directory, string projectName) => From 10d1cd799d4454aedae9e89c5f2fe2f09ef2de70 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 19 Nov 2023 15:17:18 +0100 Subject: [PATCH 42/53] (#354) CodeGen: add integration tests --- .../Cesium.CodeGen.Tests.csproj | 1 + .../CodeGenNetInteropTests.cs | 46 ++++++++++++++++--- Cesium.CodeGen.Tests/CodeGenTestBase.cs | 30 ++++++++---- ...rInterop_architecture=Dynamic.verified.txt | 4 +- ...CPtrInterop_architecture=Wide.verified.txt | 4 +- ...rInterop_architecture=Dynamic.verified.txt | 14 ++++-- ...cPtrInterop_architecture=Wide.verified.txt | 14 ++++-- ...rInterop_architecture=Dynamic.verified.txt | 4 +- ...nterInterop_architecture=Wide.verified.txt | 4 +- ...rInterop_architecture=Dynamic.verified.txt | 4 +- ...nterInterop_architecture=Wide.verified.txt | 4 +- ...rInterop_architecture=Dynamic.verified.txt | 4 +- ...dPtrInterop_architecture=Wide.verified.txt | 4 +- Cesium.CodeGen/Contexts/AssemblyContext.cs | 12 ++++- ...cTypeCache.cs => ConversionMethodCache.cs} | 17 +++---- .../Ir/Expressions/TypeCastExpression.cs | 11 +++-- Cesium.CodeGen/Ir/Types/InteropType.cs | 43 +++++++++++++---- Cesium.Compiler/Cesium.Compiler.csproj | 1 + Cesium.Compiler/RuntimeConfig.cs | 13 ++++++ .../CSharpCompilationUtil.cs | 5 +- 20 files changed, 175 insertions(+), 64 deletions(-) rename Cesium.CodeGen/Contexts/Utilities/{GenericTypeCache.cs => ConversionMethodCache.cs} (69%) diff --git a/Cesium.CodeGen.Tests/Cesium.CodeGen.Tests.csproj b/Cesium.CodeGen.Tests/Cesium.CodeGen.Tests.csproj index a5d0a61c..73cd85c4 100644 --- a/Cesium.CodeGen.Tests/Cesium.CodeGen.Tests.csproj +++ b/Cesium.CodeGen.Tests/Cesium.CodeGen.Tests.csproj @@ -24,6 +24,7 @@ + diff --git a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs index a83f9a39..c78e6aff 100644 --- a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs @@ -1,9 +1,11 @@ using System.Diagnostics.CodeAnalysis; +using Cesium.Compiler; using Cesium.Test.Framework; using Xunit.Abstractions; namespace Cesium.CodeGen.Tests; +// TODO: Make them run in parallel, as all the integration tests public class CodeGenNetInteropTests : CodeGenTestBase { private readonly ITestOutputHelper _output; @@ -21,8 +23,40 @@ private async Task DoTest( _output, CSharpCompilationUtil.DefaultRuntime, cSharpCode); - var cesiumAssembly = GenerateAssembly(runtime: null, arch: architecture, sources: new[]{cCode}, referencePaths: new[] { cSharpAssemblyPath }); + var (cesiumAssembly, assemblyContents) = GenerateAssembly(runtime: null, arch: architecture, sources: new[]{cCode}, referencePaths: new[] { cSharpAssemblyPath }); await VerifyTypes(cesiumAssembly, architecture); + await VerifyAssemblyRuns(assemblyContents.ToArray(), cSharpAssemblyPath); + } + + private async Task VerifyAssemblyRuns(byte[] assemblyContentToRun, string referencePath) + { + var testDirectoryPath = Path.GetTempFileName(); + File.Delete(testDirectoryPath); + Directory.CreateDirectory(testDirectoryPath); + + try + { + var assemblyPath = Path.Combine(testDirectoryPath, "EntryPoint.dll"); + var runtimeConfigPath = Path.ChangeExtension(assemblyPath, ".runtimeconfig.json"); + + await File.WriteAllBytesAsync(assemblyPath, assemblyContentToRun); + await File.WriteAllTextAsync(runtimeConfigPath, RuntimeConfig.EmitNet7()); + + DeployReferenceAssembly(CSharpCompilationUtil.CesiumRuntimeLibraryPath); + DeployReferenceAssembly(referencePath); + + await ExecUtil.RunToSuccess(_output, "dotnet", testDirectoryPath, new[] { assemblyPath }); + } + finally + { + Directory.Delete(testDirectoryPath, recursive: true); + } + + void DeployReferenceAssembly(string assemblyPath) + { + var targetFilePath = Path.Combine(testDirectoryPath, Path.GetFileName(assemblyPath)); + File.Copy(assemblyPath, targetFilePath); + } } [Theory] @@ -41,7 +75,7 @@ public Task PointerInterop(TargetArchitectureSet architecture) => DoTest( int main(void) { int x = 0; - return Func(&x); + return Func(&x) - 1; } """); @@ -62,7 +96,7 @@ public static class Test int main(void) { int x = 0; - return Func(&x); + return Func(&x) - 1; } """); @@ -83,7 +117,7 @@ public static class Test int main(void) { int x = 0; - return Func(&x); + return Func(&x) - 1; } """); @@ -108,7 +142,7 @@ int myFunc() int main(void) { - return Func(&myFunc); + return Func(&myFunc) - 1; } """); @@ -133,7 +167,7 @@ int myFunc() int main(void) { - return Func(&myFunc); + return Func(&myFunc) - 1; } """); } diff --git a/Cesium.CodeGen.Tests/CodeGenTestBase.cs b/Cesium.CodeGen.Tests/CodeGenTestBase.cs index e92ac335..97d4887d 100644 --- a/Cesium.CodeGen.Tests/CodeGenTestBase.cs +++ b/Cesium.CodeGen.Tests/CodeGenTestBase.cs @@ -15,16 +15,28 @@ namespace Cesium.CodeGen.Tests; [UseInvariantCulture] public abstract class CodeGenTestBase : VerifyTestBase { - protected static AssemblyDefinition GenerateAssembly(TargetRuntimeDescriptor? runtime, params string[] sources) => - GenerateAssembly(sources, runtime, TargetArchitectureSet.Dynamic, "", "", Array.Empty()); + protected static AssemblyDefinition GenerateAssembly(TargetRuntimeDescriptor? runtime, params string[] sources) + { + var (assembly, _) = GenerateAssembly( + sources, + runtime, + @namespace: "", + globalTypeFqn: "", + referencePaths: Array.Empty()); + return assembly; + } + protected static AssemblyDefinition GenerateAssembly( TargetRuntimeDescriptor? runtime, TargetArchitectureSet arch = TargetArchitectureSet.Dynamic, string @namespace = "", - string globalTypeFqn = "", params string[] sources) => - GenerateAssembly(sources, runtime, arch, @namespace, globalTypeFqn, Array.Empty()); + string globalTypeFqn = "", params string[] sources) + { + var (assembly, _) = GenerateAssembly(sources, runtime, arch, @namespace, globalTypeFqn, Array.Empty()); + return assembly; + } - protected static AssemblyDefinition GenerateAssembly( + protected static (AssemblyDefinition, byte[]) GenerateAssembly( string[] sources, TargetRuntimeDescriptor? runtime = null, TargetArchitectureSet arch = TargetArchitectureSet.Dynamic, @@ -104,7 +116,7 @@ private static void GenerateCode(AssemblyContext context, IEnumerable so } } - private static AssemblyDefinition EmitAssembly(AssemblyContext context) + private static (AssemblyDefinition, byte[]) EmitAssembly(AssemblyContext context) { var assembly = context.VerifyAndGetAssembly(); @@ -112,7 +124,7 @@ private static AssemblyDefinition EmitAssembly(AssemblyContext context) using var stream = new MemoryStream(); assembly.Write(stream); - return assembly; + return (assembly, stream.ToArray()); } [MustUseReturnValue] @@ -138,7 +150,7 @@ protected static Task VerifyMethods(TypeDefinition type, params object[] paramet } [MustUseReturnValue] - protected static Task VerifyMethods(IEnumerable types, params object[] parameters) + protected static Task VerifyMethods(IEnumerable types, params object[] parameters) { var result = new StringBuilder(); int i = 0; @@ -149,7 +161,7 @@ protected static Task VerifyMethods(IEnumerable types, params ob result.AppendLine(); } - DumpMethods(type, result); + DumpMethods(type!, result); i++; } diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Dynamic.verified.txt index 5cc38517..01b70310 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Dynamic.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Dynamic.verified.txt @@ -10,7 +10,9 @@ IL_0004: conv.i IL_0005: call Cesium.Runtime.CPtr`1 Cesium.Runtime.CPtr`1::op_Implicit(T*) IL_000a: call System.Int32 Test::Func(Cesium.Runtime.CPtr`1) - IL_000f: ret + IL_000f: ldc.i4.1 + IL_0010: sub + IL_0011: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt index 5cc38517..01b70310 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.CPtrInterop_architecture=Wide.verified.txt @@ -10,7 +10,9 @@ IL_0004: conv.i IL_0005: call Cesium.Runtime.CPtr`1 Cesium.Runtime.CPtr`1::op_Implicit(T*) IL_000a: call System.Int32 Test::Func(Cesium.Runtime.CPtr`1) - IL_000f: ret + IL_000f: ldc.i4.1 + IL_0010: sub + IL_0011: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Dynamic.verified.txt index d58cb886..f287a68d 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Dynamic.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Dynamic.verified.txt @@ -6,11 +6,15 @@ IL_0001: ret System.Int32 ::main() - IL_0000: ldftn System.Int32 ::myFunc() - IL_0006: conv.i - IL_0007: newobj Cesium.Runtime.FuncPtr`1 Cesium.Runtime.FuncPtr`1>::.ctor(System.Void*) - IL_000c: call System.Int32 Test::Func(Cesium.Runtime.FuncPtr`1>) - IL_0011: ret + Locals: + Cesium.Runtime.FuncPtr`1> V_0 + IL_0000: ldloca V_0 + IL_0004: ldftn System.Int32 ::myFunc() + IL_000a: conv.i + IL_000b: call System.Int32 Test::Func(Cesium.Runtime.FuncPtr`1>) + IL_0010: ldc.i4.1 + IL_0011: sub + IL_0012: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Wide.verified.txt index d58cb886..f287a68d 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Wide.verified.txt @@ -6,11 +6,15 @@ IL_0001: ret System.Int32 ::main() - IL_0000: ldftn System.Int32 ::myFunc() - IL_0006: conv.i - IL_0007: newobj Cesium.Runtime.FuncPtr`1 Cesium.Runtime.FuncPtr`1>::.ctor(System.Void*) - IL_000c: call System.Int32 Test::Func(Cesium.Runtime.FuncPtr`1>) - IL_0011: ret + Locals: + Cesium.Runtime.FuncPtr`1> V_0 + IL_0000: ldloca V_0 + IL_0004: ldftn System.Int32 ::myFunc() + IL_000a: conv.i + IL_000b: call System.Int32 Test::Func(Cesium.Runtime.FuncPtr`1>) + IL_0010: ldc.i4.1 + IL_0011: sub + IL_0012: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Dynamic.verified.txt index c824729d..194c527d 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Dynamic.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Dynamic.verified.txt @@ -8,7 +8,9 @@ System.Int32 ::main() IL_0000: ldftn System.Int32 ::myFunc() IL_0006: call System.Int32 Test::Func(method System.Int32 *()) - IL_000b: ret + IL_000b: ldc.i4.1 + IL_000c: sub + IL_000d: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Wide.verified.txt index c824729d..194c527d 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FunctionPointerInterop_architecture=Wide.verified.txt @@ -8,7 +8,9 @@ System.Int32 ::main() IL_0000: ldftn System.Int32 ::myFunc() IL_0006: call System.Int32 Test::Func(method System.Int32 *()) - IL_000b: ret + IL_000b: ldc.i4.1 + IL_000c: sub + IL_000d: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Dynamic.verified.txt index 6f882fdc..6dd62023 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Dynamic.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Dynamic.verified.txt @@ -8,7 +8,9 @@ IL_0001: stloc.0 IL_0002: ldloca.s V_0 IL_0004: call System.Int32 Test::Func(System.Int32*) - IL_0009: ret + IL_0009: ldc.i4.1 + IL_000a: sub + IL_000b: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Wide.verified.txt index 6f882fdc..6dd62023 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.PointerInterop_architecture=Wide.verified.txt @@ -8,7 +8,9 @@ IL_0001: stloc.0 IL_0002: ldloca.s V_0 IL_0004: call System.Int32 Test::Func(System.Int32*) - IL_0009: ret + IL_0009: ldc.i4.1 + IL_000a: sub + IL_000b: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Dynamic.verified.txt index 38931ba8..15740a0e 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Dynamic.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Dynamic.verified.txt @@ -10,7 +10,9 @@ IL_0004: conv.i IL_0005: call Cesium.Runtime.VoidPtr Cesium.Runtime.VoidPtr::op_Implicit(System.Void*) IL_000a: call System.Int32 Test::Func(Cesium.Runtime.VoidPtr) - IL_000f: ret + IL_000f: ldc.i4.1 + IL_0010: sub + IL_0011: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Wide.verified.txt index 38931ba8..15740a0e 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.VoidPtrInterop_architecture=Wide.verified.txt @@ -10,7 +10,9 @@ IL_0004: conv.i IL_0005: call Cesium.Runtime.VoidPtr Cesium.Runtime.VoidPtr::op_Implicit(System.Void*) IL_000a: call System.Int32 Test::Func(Cesium.Runtime.VoidPtr) - IL_000f: ret + IL_000f: ldc.i4.1 + IL_0010: sub + IL_0011: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen/Contexts/AssemblyContext.cs b/Cesium.CodeGen/Contexts/AssemblyContext.cs index 365443c0..09814bc4 100644 --- a/Cesium.CodeGen/Contexts/AssemblyContext.cs +++ b/Cesium.CodeGen/Contexts/AssemblyContext.cs @@ -137,13 +137,21 @@ TypeDefinition GetRuntimeType(string typeName) => throw new AssertException($"Could not find type {typeName} in the runtime assembly."); _runtimeCPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.CPtrFullTypeName)); - _cPtrConverterCache = new ConversionMethodCache(_runtimeCPtr, "op_Implicit", Module); + _cPtrConverterCache = new ConversionMethodCache( + _runtimeCPtr, + ReturnType: _runtimeCPtr.MakeGenericInstanceType(_runtimeCPtr.GenericParameters.Single()), + "op_Implicit", + Module); RuntimeVoidPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.VoidPtrFullTypeName)); _voidPtrConverter = new(() => GetImplicitCastOperator(TypeSystemEx.VoidPtrFullTypeName)); _runtimeFuncPtr = Module.ImportReference(GetRuntimeType(TypeSystemEx.FuncPtrFullTypeName)); - _funcPtrConstructorCache = new ConversionMethodCache(_runtimeFuncPtr, ".ctor", Module); + _funcPtrConstructorCache = new ConversionMethodCache( + _runtimeFuncPtr, + ReturnType: null, + ".ctor", + Module); _importedActionDelegates = new("System", "Action", Module); _importedFuncDelegates = new("System", "Func", Module); diff --git a/Cesium.CodeGen/Contexts/Utilities/GenericTypeCache.cs b/Cesium.CodeGen/Contexts/Utilities/ConversionMethodCache.cs similarity index 69% rename from Cesium.CodeGen/Contexts/Utilities/GenericTypeCache.cs rename to Cesium.CodeGen/Contexts/Utilities/ConversionMethodCache.cs index f4b0fa2a..38021f43 100644 --- a/Cesium.CodeGen/Contexts/Utilities/GenericTypeCache.cs +++ b/Cesium.CodeGen/Contexts/Utilities/ConversionMethodCache.cs @@ -14,6 +14,7 @@ namespace Cesium.CodeGen.Contexts.Utilities; /// internal record ConversionMethodCache( TypeReference GenericType, + TypeReference? ReturnType, string MethodName, ModuleDefinition TargetModule) { @@ -33,17 +34,11 @@ private MethodReference FindMethod(TypeReference argument) { var genericMethod = GenericType.Resolve().Methods.Single(m => m.Name == MethodName); var declaringType = GenericType.MakeGenericInstanceType(argument); - var methodReference = new MethodReference( - genericMethod.Name, - returnType: GenericType.MakeGenericInstanceType( - GenericType.GenericParameters.Single()), - declaringType); - foreach (var p in genericMethod.Parameters) - { - methodReference.Parameters.Add( - new ParameterDefinition(p.Name, p.Attributes, p.ParameterType)); - } + var methodReference = TargetModule.ImportReference(genericMethod); + methodReference.DeclaringType = declaringType; + if (ReturnType != null) + methodReference.ReturnType = ReturnType; - return TargetModule.ImportReference(methodReference); + return methodReference; } } diff --git a/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs b/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs index 6c525b98..b59f178b 100644 --- a/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs +++ b/Cesium.CodeGen/Ir/Expressions/TypeCastExpression.cs @@ -29,6 +29,12 @@ public TypeCastExpression(CastExpression castExpression) public void EmitTo(IEmitScope scope) { + if (TargetType is InteropType iType) + { + iType.EmitConversion(scope, Expression); + return; + } + Expression.EmitTo(scope); var ts = scope.CTypeSystem; @@ -56,11 +62,6 @@ 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) - { - Add(OpCodes.Conv_I); // TODO: Should only emit if required. - scope.Method.Body.Instructions.Add(iType.GetConvertInstruction(scope.AssemblyContext)); - } else throw new AssertException($"Type {TargetType} is not supported."); diff --git a/Cesium.CodeGen/Ir/Types/InteropType.cs b/Cesium.CodeGen/Ir/Types/InteropType.cs index e758ade2..eaa93783 100644 --- a/Cesium.CodeGen/Ir/Types/InteropType.cs +++ b/Cesium.CodeGen/Ir/Types/InteropType.cs @@ -1,5 +1,6 @@ using Cesium.CodeGen.Contexts; using Cesium.CodeGen.Extensions; +using Cesium.CodeGen.Ir.Expressions; using Cesium.Core; using Mono.Cecil; using Mono.Cecil.Cil; @@ -33,24 +34,46 @@ internal record InteropType(TypeReference UnderlyingType) : IType $"{nameof(InteropType)} doesn't know how to get size of an underlying {UnderlyingType}."); } - public Instruction GetConvertInstruction(AssemblyContext context) + public void EmitConversion(IEmitScope scope, IExpression expression) { + void EmitExprAndGetPtr() + { + expression.EmitTo(scope); + scope.AddInstruction(OpCodes.Conv_I); // TODO: Should only emit if required. + } + + var assemblyContext = scope.AssemblyContext; if (UnderlyingType.FullName == TypeSystemEx.VoidPtrFullTypeName) - return Instruction.Create(OpCodes.Call, context.VoidPtrConverter); + { + EmitExprAndGetPtr(); + scope.AddInstruction(OpCodes.Call, assemblyContext.VoidPtrConverter); + return; + } if (UnderlyingType is GenericInstanceType typeInstance) { var parent = typeInstance.GetElementType(); - return parent.FullName switch + switch (parent.FullName) { - TypeSystemEx.CPtrFullTypeName => - Instruction.Create(OpCodes.Call, context.CPtrConverter(typeInstance.GenericArguments.Single())), - TypeSystemEx.FuncPtrFullTypeName => + case TypeSystemEx.CPtrFullTypeName: + EmitExprAndGetPtr(); + scope.AddInstruction( + OpCodes.Call, + assemblyContext.CPtrConverter(typeInstance.GenericArguments.Single())); + break; + case TypeSystemEx.FuncPtrFullTypeName: + var funcPtrVariable = new VariableDefinition(UnderlyingType); + scope.Method.Body.Variables.Add(funcPtrVariable); + scope.AddInstruction(OpCodes.Ldloca, funcPtrVariable); // TODO: Use common mechanism to efficiently address local variables, use ldloca.s when necessary + EmitExprAndGetPtr(); Instruction.Create( - OpCodes.Newobj, - context.FuncPtrConstructor(typeInstance.GenericArguments.Single())), - _ => throw new AssertException($"No conversion available for interop type {parent}.") - }; + OpCodes.Call, + assemblyContext.FuncPtrConstructor(typeInstance.GenericArguments.Single())); + break; + default: + throw new AssertException($"No conversion available for interop type {parent}."); + } + return; } throw new AssertException( diff --git a/Cesium.Compiler/Cesium.Compiler.csproj b/Cesium.Compiler/Cesium.Compiler.csproj index 09a27547..ec2a202e 100644 --- a/Cesium.Compiler/Cesium.Compiler.csproj +++ b/Cesium.Compiler/Cesium.Compiler.csproj @@ -25,6 +25,7 @@ + diff --git a/Cesium.Compiler/RuntimeConfig.cs b/Cesium.Compiler/RuntimeConfig.cs index f5c7acf7..2b75217d 100644 --- a/Cesium.Compiler/RuntimeConfig.cs +++ b/Cesium.Compiler/RuntimeConfig.cs @@ -14,4 +14,17 @@ public static string EmitNet6() => """ } } """.ReplaceLineEndings("\n"); + + public static string EmitNet7() => """ + { + "runtimeOptions": { + "tfm": "net7.0", + "rollForward": "Major", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "7.0.0" + } + } + } + """.ReplaceLineEndings("\n"); } diff --git a/Cesium.Test.Framework/CSharpCompilationUtil.cs b/Cesium.Test.Framework/CSharpCompilationUtil.cs index db20a668..1dbf4bb7 100644 --- a/Cesium.Test.Framework/CSharpCompilationUtil.cs +++ b/Cesium.Test.Framework/CSharpCompilationUtil.cs @@ -62,9 +62,8 @@ await ExecUtil.RunToSuccess( project.Add(new XElement("PropertyGroup", new XElement(new XElement("AllowUnsafeBlocks", "true")))); - var runtimeLibraryPath = GetCesiumRuntimeLibraryPath(); project.Add(new XElement("ItemGroup", - new XElement("Reference", new XAttribute("Include", runtimeLibraryPath)))); + new XElement("Reference", new XAttribute("Include", CesiumRuntimeLibraryPath)))); await using var outputStream = new FileStream(projectFilePath, FileMode.Truncate, FileAccess.Write); await csProj.SaveAsync(outputStream, SaveOptions.None, CancellationToken.None); @@ -72,7 +71,7 @@ await ExecUtil.RunToSuccess( return projectDirectory; } - private static string GetCesiumRuntimeLibraryPath() => Path.Combine( + public static readonly string CesiumRuntimeLibraryPath = Path.Combine( TestStructureUtil.SolutionRootPath, "Cesium.Runtime", "bin", From 193c09093cdfcef2ef8865bacc8f02c29a50c73d Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 19 Nov 2023 21:36:06 +0100 Subject: [PATCH 43/53] (#354) CodeGen: proper FuncPtr conversion --- ...PtrInterop_architecture=Dynamic.verified.txt | 16 +++++++--------- ...uncPtrInterop_architecture=Wide.verified.txt | 16 +++++++--------- Cesium.CodeGen/Ir/Types/InteropType.cs | 17 ++++------------- 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Dynamic.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Dynamic.verified.txt index f287a68d..6df99378 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Dynamic.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Dynamic.verified.txt @@ -6,15 +6,13 @@ IL_0001: ret System.Int32 ::main() - Locals: - Cesium.Runtime.FuncPtr`1> V_0 - IL_0000: ldloca V_0 - IL_0004: ldftn System.Int32 ::myFunc() - IL_000a: conv.i - IL_000b: call System.Int32 Test::Func(Cesium.Runtime.FuncPtr`1>) - IL_0010: ldc.i4.1 - IL_0011: sub - IL_0012: ret + IL_0000: ldftn System.Int32 ::myFunc() + IL_0006: conv.i + IL_0007: newobj System.Void Cesium.Runtime.FuncPtr`1>::.ctor(System.Void*) + IL_000c: call System.Int32 Test::Func(Cesium.Runtime.FuncPtr`1>) + IL_0011: ldc.i4.1 + IL_0012: sub + IL_0013: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Wide.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Wide.verified.txt index f287a68d..6df99378 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Wide.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenNetInteropTests.FuncPtrInterop_architecture=Wide.verified.txt @@ -6,15 +6,13 @@ IL_0001: ret System.Int32 ::main() - Locals: - Cesium.Runtime.FuncPtr`1> V_0 - IL_0000: ldloca V_0 - IL_0004: ldftn System.Int32 ::myFunc() - IL_000a: conv.i - IL_000b: call System.Int32 Test::Func(Cesium.Runtime.FuncPtr`1>) - IL_0010: ldc.i4.1 - IL_0011: sub - IL_0012: ret + IL_0000: ldftn System.Int32 ::myFunc() + IL_0006: conv.i + IL_0007: newobj System.Void Cesium.Runtime.FuncPtr`1>::.ctor(System.Void*) + IL_000c: call System.Int32 Test::Func(Cesium.Runtime.FuncPtr`1>) + IL_0011: ldc.i4.1 + IL_0012: sub + IL_0013: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen/Ir/Types/InteropType.cs b/Cesium.CodeGen/Ir/Types/InteropType.cs index eaa93783..058500d3 100644 --- a/Cesium.CodeGen/Ir/Types/InteropType.cs +++ b/Cesium.CodeGen/Ir/Types/InteropType.cs @@ -36,16 +36,12 @@ internal record InteropType(TypeReference UnderlyingType) : IType public void EmitConversion(IEmitScope scope, IExpression expression) { - void EmitExprAndGetPtr() - { - expression.EmitTo(scope); - scope.AddInstruction(OpCodes.Conv_I); // TODO: Should only emit if required. - } + expression.EmitTo(scope); + scope.AddInstruction(OpCodes.Conv_I); // TODO: Should only emit if required. var assemblyContext = scope.AssemblyContext; if (UnderlyingType.FullName == TypeSystemEx.VoidPtrFullTypeName) { - EmitExprAndGetPtr(); scope.AddInstruction(OpCodes.Call, assemblyContext.VoidPtrConverter); return; } @@ -56,18 +52,13 @@ void EmitExprAndGetPtr() switch (parent.FullName) { case TypeSystemEx.CPtrFullTypeName: - EmitExprAndGetPtr(); scope.AddInstruction( OpCodes.Call, assemblyContext.CPtrConverter(typeInstance.GenericArguments.Single())); break; case TypeSystemEx.FuncPtrFullTypeName: - var funcPtrVariable = new VariableDefinition(UnderlyingType); - scope.Method.Body.Variables.Add(funcPtrVariable); - scope.AddInstruction(OpCodes.Ldloca, funcPtrVariable); // TODO: Use common mechanism to efficiently address local variables, use ldloca.s when necessary - EmitExprAndGetPtr(); - Instruction.Create( - OpCodes.Call, + scope.AddInstruction( + OpCodes.Newobj, assemblyContext.FuncPtrConstructor(typeInstance.GenericArguments.Single())); break; default: From 44c0f1aa52fe7ae3d4188f4cd52784c6eb4879ee Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Tue, 21 Nov 2023 22:59:19 +0100 Subject: [PATCH 44/53] (#354) CodeGen: add an integration test for function pointers inside of a struct --- .../ArchitectureDependentCodeTests.cs | 2 +- .../Cesium.IntegrationTests.csproj | 4 ++++ .../pointers/pointers_functions.c | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 Cesium.IntegrationTests/pointers/pointers_functions.c diff --git a/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs b/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs index f6b66a99..39eb8bb2 100644 --- a/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs +++ b/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs @@ -122,5 +122,5 @@ typedef struct { hostFunc foo; } foo; -"""); // TODO: Check if the resulting field signature works on CLR at all +"""); } diff --git a/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj b/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj index 499155c8..7d5599b8 100644 --- a/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj +++ b/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj @@ -25,4 +25,8 @@ + + + + diff --git a/Cesium.IntegrationTests/pointers/pointers_functions.c b/Cesium.IntegrationTests/pointers/pointers_functions.c new file mode 100644 index 00000000..7cd22879 --- /dev/null +++ b/Cesium.IntegrationTests/pointers/pointers_functions.c @@ -0,0 +1,12 @@ +typedef int (*func)(void); +typedef int (*hostFunc)(func); +typedef struct +{ + hostFunc foo; +} foo; + +int main(void) +{ + foo x; + return 42; +} From 72ced1bda5849146e4d1bd4be963d6e2fed4b2cc Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Tue, 21 Nov 2023 23:35:32 +0100 Subject: [PATCH 45/53] (#354) TODO sweep, better typoe checks --- Cesium.CodeGen/Extensions/TypeSystemEx.cs | 25 ++++++++++++++----- .../CSharpCompilationUtil.cs | 2 -- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Cesium.CodeGen/Extensions/TypeSystemEx.cs b/Cesium.CodeGen/Extensions/TypeSystemEx.cs index 2d076083..1ec35820 100644 --- a/Cesium.CodeGen/Extensions/TypeSystemEx.cs +++ b/Cesium.CodeGen/Extensions/TypeSystemEx.cs @@ -160,18 +160,31 @@ private static bool TypesCorrespond(TypeSystem typeSystem, TypeReference type1, return type1 is PointerType pt && pt.ElementType.IsEqualTo(typeSystem.Void); } - if (!type2.IsGenericInstance) return false; - type2 = type2.GetElementType(); + if (type2 is not GenericInstanceType type2Instance) return false; + var type2Definition = type2.GetElementType(); if (type1.IsPointer) { - // TODO: Analyze the generic argument. - return type2.FullName == CPtrFullTypeName; + if (type2Definition.FullName != CPtrFullTypeName) + { + // Non-pointer gets compared to a pointer. + return false; + } + + var pointed1 = ((PointerType)type1).ElementType; + var pointed2 = type2Instance.GenericArguments.Single(); + return TypesCorrespond(typeSystem, pointed1, pointed2); } if (type1.IsFunctionPointer) { - // TODO: Analyze the generic argument. - return type2.FullName == FuncPtrFullTypeName; + if (type2Definition.FullName != FuncPtrFullTypeName) + { + // A function pointer gets compared to not a function pointer. + return false; + } + + // TODO: Compare the function type signatures here. + return true; } throw new AssertException("Impossible: type1 should be either a pointer or a function pointer."); diff --git a/Cesium.Test.Framework/CSharpCompilationUtil.cs b/Cesium.Test.Framework/CSharpCompilationUtil.cs index 1dbf4bb7..255195f5 100644 --- a/Cesium.Test.Framework/CSharpCompilationUtil.cs +++ b/Cesium.Test.Framework/CSharpCompilationUtil.cs @@ -15,10 +15,8 @@ public static class CSharpCompilationUtil private const string _projectName = "TestProject"; /// Semaphore that controls the amount of simultaneously running tests. - // TODO: Should not be static, make a fixture. private static readonly SemaphoreSlim _testSemaphore = new(Environment.ProcessorCount); - // TODO: Support references public static async Task CompileCSharpAssembly( ITestOutputHelper output, TargetRuntimeDescriptor runtime, From 69a5c06bfa74e40525d3f395d279bdf650d0b250 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 23 Nov 2023 23:04:45 +0100 Subject: [PATCH 46/53] (#354) FunctionInfo: add a TODO --- Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs b/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs index cfe0e641..cfc3a1f4 100644 --- a/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs +++ b/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs @@ -7,6 +7,7 @@ namespace Cesium.CodeGen.Contexts.Meta; +// TODO: This is confusing, make immutable. internal record FunctionInfo( ParametersInfo? Parameters, IType ReturnType, From 4b2783f141f8099ad8ce664bc6072bf285ecdb2d Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 23 Nov 2023 23:10:30 +0100 Subject: [PATCH 47/53] (#354) Integration tests: drop an incorrect project item --- Cesium.IntegrationTests/Cesium.IntegrationTests.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj b/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj index 7d5599b8..499155c8 100644 --- a/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj +++ b/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj @@ -25,8 +25,4 @@ - - - - From ad2fcd6aba2c22e9a7411fa012457f972d6c61f6 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 23 Nov 2023 23:11:11 +0100 Subject: [PATCH 48/53] (#354) IntegrationTestContext: code cleanup --- Cesium.IntegrationTests/IntegrationTestContext.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Cesium.IntegrationTests/IntegrationTestContext.cs b/Cesium.IntegrationTests/IntegrationTestContext.cs index 4581830c..b0c054b2 100644 --- a/Cesium.IntegrationTests/IntegrationTestContext.cs +++ b/Cesium.IntegrationTests/IntegrationTestContext.cs @@ -1,5 +1,4 @@ using Cesium.Test.Framework; -using Cesium.Test.Framework; using JetBrains.Annotations; using NeoSmart.AsyncLock; using Xunit.Abstractions; From 443597ef114f08f2c10bb8c31e9cb88b0909c20a Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 23 Nov 2023 23:14:44 +0100 Subject: [PATCH 49/53] (#354) Docs: small improvement --- docs/type-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/type-system.md b/docs/type-system.md index 73dd757a..c32bd5a7 100644 --- a/docs/type-system.md +++ b/docs/type-system.md @@ -31,7 +31,7 @@ This should be kept in sync with section **6.7.2 Type Specifiers** of the actual All the pointer types are mapped to the CLI pointers of the corresponding type on **dynamic**, **32b** and **64b** architecture sets. -The **wide** architecture set supports mapping to raw pointers as well, but also supports special mapping types that have architecture-independent size and memory alignment, according to the following table. +The **wide** architecture set supports mapping to raw pointers as well, but supports additional types that have architecture-independent size and memory alignment, according to the following table. | C type | CLI Type | |--------------------------------------------------------|-------------------------------------| From b76311e605cd50ab56459317881271672ac632dd Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 23 Nov 2023 23:37:39 +0100 Subject: [PATCH 50/53] (#78, #285, #487, #488, #489, #490, #491, #492) Connect TODOs with the issues --- Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs | 4 ++-- Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs | 2 +- Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs | 2 +- Cesium.CodeGen/Contexts/TranslationUnitContext.cs | 2 +- Cesium.CodeGen/Extensions/TypeSystemEx.cs | 2 +- Cesium.CodeGen/Ir/Expressions/FunctionCallExpressionBase.cs | 2 +- Cesium.CodeGen/Ir/Types/InteropType.cs | 2 +- Cesium.Parser/CParser.cs | 2 +- Cesium.Runtime/FuncPtr.cs | 2 +- Cesium.Test.Framework/CSharpCompilationUtil.cs | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs b/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs index 39eb8bb2..f3d3b02c 100644 --- a/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs +++ b/Cesium.CodeGen.Tests/ArchitectureDependentCodeTests.cs @@ -107,8 +107,8 @@ struct Foo func x; }; """); - // TODO: empty-paren-func ptr - // TODO: vararg-func ptr + // TODO[#487]: empty-paren-func ptr + // TODO[#487]: vararg-func ptr [Theory] [InlineData(TargetArchitectureSet.Dynamic)] diff --git a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs index c78e6aff..cbc31eef 100644 --- a/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenNetInteropTests.cs @@ -5,7 +5,7 @@ namespace Cesium.CodeGen.Tests; -// TODO: Make them run in parallel, as all the integration tests +// TODO[#488]: Make them run in parallel, as all the integration tests public class CodeGenNetInteropTests : CodeGenTestBase { private readonly ITestOutputHelper _output; diff --git a/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs b/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs index cfc3a1f4..4b1b987e 100644 --- a/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs +++ b/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs @@ -7,7 +7,7 @@ namespace Cesium.CodeGen.Contexts.Meta; -// TODO: This is confusing, make immutable. +// TODO[#489]: This is confusing, make immutable. internal record FunctionInfo( ParametersInfo? Parameters, IType ReturnType, diff --git a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs index 556f5b49..18676f86 100644 --- a/Cesium.CodeGen/Contexts/TranslationUnitContext.cs +++ b/Cesium.CodeGen/Contexts/TranslationUnitContext.cs @@ -239,7 +239,7 @@ List ProcessParameters(ICollection parameters) { var areParametersValid = implementation.Parameters.Count == parameters.Count - || declaration.Parameters.IsVarArg; // TODO: A better check for interop functions + vararg. + || declaration.Parameters.IsVarArg; // TODO[#487]: A better check for interop functions + vararg. if (!areParametersValid) { throw new CompilationException( diff --git a/Cesium.CodeGen/Extensions/TypeSystemEx.cs b/Cesium.CodeGen/Extensions/TypeSystemEx.cs index 1ec35820..08b2c5c0 100644 --- a/Cesium.CodeGen/Extensions/TypeSystemEx.cs +++ b/Cesium.CodeGen/Extensions/TypeSystemEx.cs @@ -183,7 +183,7 @@ private static bool TypesCorrespond(TypeSystem typeSystem, TypeReference type1, return false; } - // TODO: Compare the function type signatures here. + // TODO[#490]: Compare the function type signatures here. return true; } diff --git a/Cesium.CodeGen/Ir/Expressions/FunctionCallExpressionBase.cs b/Cesium.CodeGen/Ir/Expressions/FunctionCallExpressionBase.cs index 266baae3..79b51909 100644 --- a/Cesium.CodeGen/Ir/Expressions/FunctionCallExpressionBase.cs +++ b/Cesium.CodeGen/Ir/Expressions/FunctionCallExpressionBase.cs @@ -21,7 +21,7 @@ protected void EmitArgumentList(IEmitScope scope, ParametersInfo? paramInfo, IRe if (paramInfo?.IsVarArg == true) { - // TODO: See https://github.com/ForNeVeR/Cesium/issues/285 + // TODO[#285]: // Using sparse population of the parameters on the stack. 8 bytes should be enough for anybody. // Also we need perform localloc on empty stack, so we will use local variable to save vararg buffer to temporary variable. if (varArgParametersCount == 0) diff --git a/Cesium.CodeGen/Ir/Types/InteropType.cs b/Cesium.CodeGen/Ir/Types/InteropType.cs index 058500d3..383cdd9e 100644 --- a/Cesium.CodeGen/Ir/Types/InteropType.cs +++ b/Cesium.CodeGen/Ir/Types/InteropType.cs @@ -37,7 +37,7 @@ internal record InteropType(TypeReference UnderlyingType) : IType public void EmitConversion(IEmitScope scope, IExpression expression) { expression.EmitTo(scope); - scope.AddInstruction(OpCodes.Conv_I); // TODO: Should only emit if required. + scope.AddInstruction(OpCodes.Conv_I); // TODO[#491]: Should only emit if required. var assemblyContext = scope.AssemblyContext; if (UnderlyingType.FullName == TypeSystemEx.VoidPtrFullTypeName) diff --git a/Cesium.Parser/CParser.cs b/Cesium.Parser/CParser.cs index d7aa25ee..2a6efd0a 100644 --- a/Cesium.Parser/CParser.cs +++ b/Cesium.Parser/CParser.cs @@ -36,7 +36,7 @@ public partial class CParser // 6.4.5 String literals - // TODO: + // TODO[#78]: // string_literal: // encoding-prefix? " s-char-sequence? " [Rule("string_literal: StringLiteral")] diff --git a/Cesium.Runtime/FuncPtr.cs b/Cesium.Runtime/FuncPtr.cs index 478136b4..98460430 100644 --- a/Cesium.Runtime/FuncPtr.cs +++ b/Cesium.Runtime/FuncPtr.cs @@ -1,7 +1,7 @@ namespace Cesium.Runtime; /// A class encapsulating a C function pointer. -public readonly unsafe struct FuncPtr where TDelegate : Delegate // TODO: Think about vararg and empty parameter list encoding. +public readonly unsafe struct FuncPtr where TDelegate : Delegate // TODO[#487]: Think about vararg and empty parameter list encoding. { private readonly long _value; diff --git a/Cesium.Test.Framework/CSharpCompilationUtil.cs b/Cesium.Test.Framework/CSharpCompilationUtil.cs index 255195f5..2c520bb5 100644 --- a/Cesium.Test.Framework/CSharpCompilationUtil.cs +++ b/Cesium.Test.Framework/CSharpCompilationUtil.cs @@ -5,7 +5,7 @@ namespace Cesium.Test.Framework; -// TODO: Make a normal disposable class to delete the whole directory in the end of the test. +// TODO[#492]: Make a normal disposable class to delete the whole directory in the end of the test. public static class CSharpCompilationUtil { public static readonly TargetRuntimeDescriptor DefaultRuntime = TargetRuntimeDescriptor.Net60; From 79e29675ed13f3010666866b5c33795cba8824a7 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 23 Nov 2023 23:42:31 +0100 Subject: [PATCH 51/53] (#487, #493) Connect TODOs with the issues --- Cesium.CodeGen/Contexts/AssemblyContext.cs | 2 +- Cesium.CodeGen/Ir/Types/FunctionType.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cesium.CodeGen/Contexts/AssemblyContext.cs b/Cesium.CodeGen/Contexts/AssemblyContext.cs index 09814bc4..81ffcad6 100644 --- a/Cesium.CodeGen/Contexts/AssemblyContext.cs +++ b/Cesium.CodeGen/Contexts/AssemblyContext.cs @@ -188,7 +188,7 @@ public TypeReference StandardDelegateType(TypeReference returnType, IEnumerable< if (typeArgumentCount > 16) { throw new WipException( - WipException.ToDo, + 493, $"Mapping of function for argument count {typeArgumentCount} is not supported."); } diff --git a/Cesium.CodeGen/Ir/Types/FunctionType.cs b/Cesium.CodeGen/Ir/Types/FunctionType.cs index 9b6280cf..9b86e8b6 100644 --- a/Cesium.CodeGen/Ir/Types/FunctionType.cs +++ b/Cesium.CodeGen/Ir/Types/FunctionType.cs @@ -23,10 +23,10 @@ public TypeReference ResolveAsDelegateType(TranslationUnitContext context) var (parameterInfos, isVoid, isVarArg) = Parameters; if (isVarArg) - throw new WipException(WipException.ToDo, $"A vararg function is not implemented, yet: {this}."); + throw new WipException(487, $"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}."); + throw new WipException(487, $"A function with an empty parameter list is not implemented, yet: {this}."); var arguments = parameterInfos.Select(p => p.Type.Resolve(context)); return context.AssemblyContext.StandardDelegateType(returnType, arguments); From 171fbee76a7fe80981cdda13e41444841a20bec2 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 23 Nov 2023 23:44:10 +0100 Subject: [PATCH 52/53] WipException.ToDo: better documentation --- Cesium.Core/Cesium.Core.csproj | 4 ++++ Cesium.Core/Exceptions/WipException.cs | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/Cesium.Core/Cesium.Core.csproj b/Cesium.Core/Cesium.Core.csproj index 6836c680..c59f7450 100644 --- a/Cesium.Core/Cesium.Core.csproj +++ b/Cesium.Core/Cesium.Core.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/Cesium.Core/Exceptions/WipException.cs b/Cesium.Core/Exceptions/WipException.cs index a22f50d8..9fc0499f 100644 --- a/Cesium.Core/Exceptions/WipException.cs +++ b/Cesium.Core/Exceptions/WipException.cs @@ -1,8 +1,15 @@ +using JetBrains.Annotations; + namespace Cesium.Core; public sealed class WipException : CesiumException { /// A marker value for an issue number not yet assigned. + /// + /// Should only be used in PRs while developing new features. Before merging to the main branch, all instances of + /// this constant should be replaced with newly assigned issue numbers. + /// + [PublicAPI] public const int ToDo = -1; public WipException(int issueNo, string additionalMessage) @@ -10,6 +17,7 @@ public WipException(int issueNo, string additionalMessage) { } + [PublicAPI] public WipException(int issueNo) : base($"This work is in progress. See https://github.com/ForNeVeR/Cesium/issues/{issueNo}.") { From d76e339805cc0c25e59d74a9d3c146f2fb3a5220 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Thu, 23 Nov 2023 23:46:49 +0100 Subject: [PATCH 53/53] Build: use the last version of JetBrains.Annotations everywhere --- Cesium.CodeGen.Tests/Cesium.CodeGen.Tests.csproj | 2 +- Cesium.IntegrationTests/Cesium.IntegrationTests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cesium.CodeGen.Tests/Cesium.CodeGen.Tests.csproj b/Cesium.CodeGen.Tests/Cesium.CodeGen.Tests.csproj index 73cd85c4..0fc07877 100644 --- a/Cesium.CodeGen.Tests/Cesium.CodeGen.Tests.csproj +++ b/Cesium.CodeGen.Tests/Cesium.CodeGen.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj b/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj index 499155c8..90c5055b 100644 --- a/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj +++ b/Cesium.IntegrationTests/Cesium.IntegrationTests.csproj @@ -12,7 +12,7 @@ - +