diff --git a/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/StringMarshaling/LLVMErrorRef.cs b/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/StringMarshaling/LLVMErrorRef.cs
index 4a4ca58b3..8d64ecfb9 100644
--- a/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/StringMarshaling/LLVMErrorRef.cs
+++ b/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/StringMarshaling/LLVMErrorRef.cs
@@ -82,7 +82,7 @@ public LLVMErrorTypeId TypeId
}
return LazyMessage.IsValueCreated
- ? LLVMGetStringErrorTypeId()
+ ? LLVMGetStringErrorTypeId() // string retrieved already so the "type" is known.
: LLVMErrorTypeId.FromABI(LLVMGetErrorTypeId(DangerousGetHandle()));
[DllImport( LibraryName )]
@@ -135,7 +135,7 @@ public static LLVMErrorRef Create( LazyEncodedString errMsg )
/// In all other cases a fully wrapped handle () is used via .
///
///
- public static unsafe nint CreateForNativeOut( LazyEncodedString errMsg )
+ public static nint CreateForNativeOut( LazyEncodedString errMsg )
{
unsafe
{
@@ -152,6 +152,14 @@ public static unsafe nint CreateForNativeOut( LazyEncodedString errMsg )
static unsafe extern nint /*LLVMErrorRef*/ LLVMCreateStringError( byte* ErrMsg );
}
+ /// Create a new as from
+ /// Exceotion to get the error message from
+ ///
+ public static nint CreateForNativeOut( Exception ex)
+ {
+ return CreateForNativeOut(ex.Message);
+ }
+
public void Dispose()
{
// if a message was previously realized, dispose of it now.
@@ -180,19 +188,6 @@ public static LLVMErrorRef FromABI( nint abiValue )
return new(abiValue);
}
- private LLVMErrorTypeId LazyGetTypeId()
- {
- // NOTE: Native API will fail (undocumented) if error message already retrieved.
- // This causes a crash in a debugger trying to show this property as ToString()
- // is already called.
- return LazyMessage.IsValueCreated ? default : LLVMErrorTypeId.FromABI(LLVMGetErrorTypeId(DangerousGetHandle()));
-
- [DllImport( LibraryName )]
- [UnmanagedCallConv( CallConvs = [ typeof( CallConvCdecl ) ] )]
- [SuppressMessage( "Interoperability", "SYSLIB1054:Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time", Justification = "Signature is P/Invoke ready" )]
- static extern /*LLVMErrorTypeId*/nint LLVMGetErrorTypeId(/*LLVMErrorRef*/ nint Err );
- }
-
private ErrorMessageString LazyGetMessage( )
{
if(IsNull)
diff --git a/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/Core.cs b/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/Core.cs
index d44606970..bc53f773a 100644
--- a/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/Core.cs
+++ b/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/Core.cs
@@ -421,7 +421,7 @@ public static partial class Core
[LibraryImport( LibraryName )]
[UnmanagedCallConv( CallConvs = [ typeof( CallConvCdecl ) ] )]
- public static unsafe partial void LLVMContextSetDiagnosticHandler( LLVMContextRefAlias C, LLVMDiagnosticHandler Handler, nint DiagnosticContext );
+ public static unsafe partial void LLVMContextSetDiagnosticHandler( LLVMContextRefAlias C, LLVMDiagnosticHandler Handler, void* DiagnosticContext );
[LibraryImport( LibraryName )]
[UnmanagedCallConv( CallConvs = [ typeof( CallConvCdecl ) ] )]
@@ -429,7 +429,7 @@ public static partial class Core
[LibraryImport( LibraryName )]
[UnmanagedCallConv( CallConvs = [ typeof( CallConvCdecl ) ] )]
- public static unsafe partial nint LLVMContextGetDiagnosticContext( LLVMContextRefAlias C );
+ public static unsafe partial void* LLVMContextGetDiagnosticContext( LLVMContextRefAlias C );
[LibraryImport( LibraryName )]
[UnmanagedCallConv( CallConvs = [ typeof( CallConvCdecl ) ] )]
diff --git a/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/ExecutionEngine.cs b/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/ExecutionEngine.cs
index 324d1d73b..47953da8e 100644
--- a/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/ExecutionEngine.cs
+++ b/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/ExecutionEngine.cs
@@ -10,9 +10,9 @@ namespace Ubiquity.NET.Llvm.Interop.ABI.llvm_c
// Misplaced using directive; It isn't misplaced - tooling is too brain dead to know the difference between an alias and a using directive
#pragma warning disable IDE0065, SA1200, SA1135
using unsafe LLVMMemoryManagerAllocateCodeSectionCallback = delegate* unmanaged[Cdecl]< void* /*Opaque*/, nuint /*Size*/, uint /*Alignment*/, uint /*SectionID*/, byte* /*SectionName*/, byte* /*retVal*/>;
- using unsafe LLVMMemoryManagerAllocateDataSectionCallback = delegate* unmanaged[Cdecl]< void* /*Opaque*/, nuint /*Size*/, uint /*Alignment*/, uint /*SectionID*/, byte* /*SectionName*/, bool /*IsReadOnly*/, byte* /*retVal*/>;
+ using unsafe LLVMMemoryManagerAllocateDataSectionCallback = delegate* unmanaged[Cdecl]< void* /*Opaque*/, nuint /*Size*/, uint /*Alignment*/, uint /*SectionID*/, byte* /*SectionName*/, /*LLVMBool*/ Int32 /*IsReadOnly*/, byte* /*retVal*/>;
using unsafe LLVMMemoryManagerDestroyCallback = delegate* unmanaged[Cdecl]< void* /*Opaque*/, void /*retVal*/ >;
- using unsafe LLVMMemoryManagerFinalizeMemoryCallback = delegate* unmanaged[Cdecl]< void* /*Opaque*/, byte** /*ErrMsg*/, bool /*retVal*/>;
+ using unsafe LLVMMemoryManagerFinalizeMemoryCallback = delegate* unmanaged[Cdecl]< void* /*Opaque*/, byte** /*ErrMsg*/, /*LLVMBool*/ Int32 /*retVal*/>;
#pragma warning restore IDE0065, SA1200, SA1135
[StructLayout( LayoutKind.Sequential )]
diff --git a/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/Orc.cs b/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/Orc.cs
index 5af120550..8b295fb67 100644
--- a/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/Orc.cs
+++ b/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/Orc.cs
@@ -207,7 +207,7 @@ public static unsafe partial LLVMOrcSymbolStringPoolEntryRef LLVMOrcExecutionSes
LazyEncodedString Name
);
- [Experimental( "LLVM001" )]
+ [Experimental( "LLVMEXP001" )]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void LLVMOrcExecutionSessionLookup(
LLVMOrcExecutionSessionRef ES,
@@ -223,7 +223,7 @@ public static unsafe void LLVMOrcExecutionSessionLookup(
[LibraryImport( LibraryName )]
[UnmanagedCallConv( CallConvs = [ typeof( CallConvCdecl ) ] )]
- [Experimental("LLVMEXP001")]
+ [Experimental("LLVMEXP002")]
private static unsafe partial void LLVMOrcExecutionSessionLookup(
LLVMOrcExecutionSessionRef ES,
LLVMOrcLookupKind K,
diff --git a/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/OrcEE.cs b/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/OrcEE.cs
index 5b288b93a..39211da25 100644
--- a/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/OrcEE.cs
+++ b/src/Interop/Ubiquity.NET.Llvm.Interop/ABI/llvm-c/OrcEE.cs
@@ -6,10 +6,10 @@ namespace Ubiquity.NET.Llvm.Interop.ABI.llvm_c
// Misplaced using directive; It isn't misplaced - tooling is too brain dead to know the difference between an alias and a using directive
#pragma warning disable IDE0065, SA1200, SA1135
using unsafe LLVMMemoryManagerAllocateCodeSectionCallback = delegate* unmanaged[Cdecl]< void* /*Opaque*/, nuint /*Size*/, uint /*Alignment*/, uint /*SectionID*/, byte* /*SectionName*/, byte* /*retVal*/>;
- using unsafe LLVMMemoryManagerAllocateDataSectionCallback = delegate* unmanaged[Cdecl]< void* /*Opaque*/, nuint /*Size*/, uint /*Alignment*/, uint /*SectionID*/, byte* /*SectionName*/, bool /*IsReadOnly*/, byte* /*retVal*/>;
+ using unsafe LLVMMemoryManagerAllocateDataSectionCallback = delegate* unmanaged[Cdecl]< void* /*Opaque*/, nuint /*Size*/, uint /*Alignment*/, uint /*SectionID*/, byte* /*SectionName*/, /*LLVMBool*/ Int32 /*IsReadOnly*/, byte* /*retVal*/>;
using unsafe LLVMMemoryManagerCreateContextCallback = delegate* unmanaged[Cdecl]< void* /*CtxCtx*/, void* /*retVal*/>;
using unsafe LLVMMemoryManagerDestroyCallback = delegate* unmanaged[Cdecl]< void* /*Opaque*/, void /*retVal*/ >;
- using unsafe LLVMMemoryManagerFinalizeMemoryCallback = delegate* unmanaged[Cdecl]< void* /*Opaque*/, byte** /*ErrMsg*/, bool /*retVal*/>;
+ using unsafe LLVMMemoryManagerFinalizeMemoryCallback = delegate* unmanaged[Cdecl]< void* /*Opaque*/, byte** /*ErrMsg*/, /*LLVMBool*/ Int32 /*retVal*/>;
using unsafe LLVMMemoryManagerNotifyTerminatingCallback = delegate* unmanaged[Cdecl]< void* /*CtxCtx*/, void /*retVal*/>;
#pragma warning restore IDE0065, SA1200, SA1135
diff --git a/src/Ubiquity.NET.CommandLine.UT/RawApiTests.cs b/src/Ubiquity.NET.CommandLine.UT/RawApiTests.cs
index 68e8ffbd5..99e190636 100644
--- a/src/Ubiquity.NET.CommandLine.UT/RawApiTests.cs
+++ b/src/Ubiquity.NET.CommandLine.UT/RawApiTests.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
using System.CommandLine;
-using System.CommandLine.Help;
using Microsoft.VisualStudio.TestTools.UnitTesting;
diff --git a/src/Ubiquity.NET.CommandLine/ParseResultExtensions.cs b/src/Ubiquity.NET.CommandLine/ParseResultExtensions.cs
index b78e809fa..d97c2dfd4 100644
--- a/src/Ubiquity.NET.CommandLine/ParseResultExtensions.cs
+++ b/src/Ubiquity.NET.CommandLine/ParseResultExtensions.cs
@@ -6,47 +6,23 @@
using System.CommandLine;
using System.CommandLine.Help;
using System.CommandLine.Parsing;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Ubiquity.NET.CommandLine
{
+ // This does NOT use the new C# 14 extension syntax due to several reasons
+ // 1) Code lens does not work https://github.com/dotnet/roslyn/issues/79006 [Sadly marked as "not planned" - e.g., dead-end]
+ // 2) MANY analyzers get things wrong and need to be supressed (CA1000, CA1034, and many others [SAxxxx])
+ // 3) Many tools (like docfx don't support the new syntax yet)
+ // 4) No clear support for Caller* attributes ([CallerArgumentExpression(...)]).
+ //
+ // Bottom line it's a good idea with an incomplete implementation lacking support
+ // in the overall ecosystem. Don't use it unless you absolutely have to until all
+ // of that is sorted out.
+
/// Utility extension methods for command line parsing
- [SuppressMessage( "Design", "CA1034:Nested types should not be visible", Justification = "BS, extension" )]
- [SuppressMessage( "Performance", "CA1822:Mark members as static", Justification = "BS, Extension" )]
public static class ParseResultExtensions
{
-#if DOCFX_BUILD_SUPPORTS_EXTENSION_KEYWORD
- extension(ParseResult self)
- {
- /// Gets a value indicating whether has any errors
- public bool HasErrors => self.Errors.Count > 0;
-
- public HelpOption? HelpOption
- {
- get
- {
- var helpOptions = from r in self.CommandResult.RecurseWhileNotNull(r => r.Parent as CommandResult)
- from o in r.Command.Options.OfType()
- select o;
-
- return helpOptions.FirstOrDefault();
- }
- }
-
- public VersionOption? VersionOption
- {
- get
- {
- var versionOptions = from r in self.CommandResult.RecurseWhileNotNull(r => r.Parent as CommandResult)
- from o in r.Command.Options.OfType()
- select o;
-
- return versionOptions.FirstOrDefault();
- }
- }
- }
-#else
/// Gets a value indicating whether has any errors
/// Result to test for errors
/// value indicating whether has any errors
@@ -75,7 +51,6 @@ from o in r.Command.Options.OfType()
return versionOptions.FirstOrDefault();
}
-#endif
// shamelessly "borrowed" from: https://github.com/dotnet/dotnet/blob/8c7b3dcd2bd657c11b12973f1214e7c3c616b174/src/command-line-api/src/System.CommandLine/Help/HelpBuilderExtensions.cs#L42
internal static IEnumerable RecurseWhileNotNull( this T? source, Func next )
diff --git a/src/Ubiquity.NET.Extensions/FluentValidationExtensions.cs b/src/Ubiquity.NET.Extensions/FluentValidationExtensions.cs
index df4023463..9690dda51 100644
--- a/src/Ubiquity.NET.Extensions/FluentValidationExtensions.cs
+++ b/src/Ubiquity.NET.Extensions/FluentValidationExtensions.cs
@@ -5,24 +5,31 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-using System.Globalization;
using System.Runtime.CompilerServices;
namespace Ubiquity.NET.Extensions
{
+ // This does NOT use the new C# 14 extension syntax due to several reasons
+ // 1) Code lens does not work https://github.com/dotnet/roslyn/issues/79006 [Sadly, marked as "not planned" - e.g., dead-end]
+ // 2) MANY analyzers get things wrong and need to be supressed (CA1000, CA1034, and many others [SAxxxx])
+ // 3) Many tools (like docfx) don't support the new syntax yet and it isn't clear if they will in the future.
+ // 4) No clear support for Caller* attributes ([CallerArgumentExpression(...)]).
+ //
+ // Bottom line it's a good idea with an incomplete implementation lacking support
+ // in the overall ecosystem. Don't use it unless you absolutely have to until all
+ // of that is sorted out.
+
/// Extension class to provide Fluent validation of arguments
///
/// These are similar to many of the built-in support checks except that
/// they use a `Fluent' style to allow validation of parameters used as inputs
/// to other functions that ultimately produce parameters for a base constructor.
/// They also serve to provide validation when using body expressions for property
- /// method implementations etc...
+ /// method implementations etc... Though the C# 14 field keyword makes that
+ /// use mostly a moot point.
///
public static class FluentValidationExtensions
{
- // NOTE: These DO NOT use the new `extension` keyword syntax as it is not clear
- // how CallerArgumentExpression is supposed to be used for those...
-
/// Throws an exception if is
/// Type of reference parameter to test for
/// Instance to test
@@ -72,9 +79,12 @@ public static T ThrowIfNotDefined( this T self, [CallerArgumentExpression( na
where T : struct, Enum
{
ArgumentNullException.ThrowIfNull(self, exp);
+
+ // TODO: Move the exception message to a resource for globalization
+ // This matches the overloaded constructor version but allows for reporting enums with non-int underlying type.
return Enum.IsDefined( typeof(T), self )
? self
- : throw new InvalidEnumArgumentException(exp);
+ : throw new InvalidEnumArgumentException($"The value of argument '{exp}' ({self}) is invalid for Enum of type '{typeof(T)}'");
}
}
}
diff --git a/src/Ubiquity.NET.InteropHelpers/MarshalGCHandle.cs b/src/Ubiquity.NET.InteropHelpers/MarshalGCHandle.cs
deleted file mode 100644
index 24331da77..000000000
--- a/src/Ubiquity.NET.InteropHelpers/MarshalGCHandle.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
-// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
-
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.InteropServices;
-
-namespace Ubiquity.NET.InteropHelpers
-{
- /// Utility class for supporting use of a GCHandle (as a raw native pointer)
- ///
- /// Many native APIs use a callback with a "Context" as a raw native pointer. It is common in
- /// such cases to use a as the context.
- ///
- public static class MarshalGCHandle
- {
- /// Try pattern to convert a as a raw native pointer
- /// Type of object the context should hold
- /// Native pointer for the context
- /// Value of the context or default if return is
- /// if the is valid and converted to
- ///
- /// This assumes that is a that was allocated and provided to native code via
- /// a call to . This will follow the try pattern to resolve
- /// back to the original instance the handle is referencing. This allows managed code callbacks to use managed objects as
- /// an opaque "context" value for native APIs.
- ///
- ///
- /// In order to satisfy nullability code analysis, call sites must declare explicitly. Otherwise,
- /// it is deduced as the type used for , which will cause analysis to complain if it isn't a nullable type.
- /// Thus, without explicit declaration of the type without nullability it is assumed nullable and the
- /// is effectively moot. So call sites should always specify the generic type parameter explicitly.
- ///
- ///
- public static unsafe bool TryGet( void* ctx, [MaybeNullWhen( false )] out T value )
- {
- if(ctx is not null && GCHandle.FromIntPtr( (nint)ctx ).Target is T self)
- {
- value = self;
- return true;
- }
-
- value = default;
- return false;
- }
- }
-}
diff --git a/src/Ubiquity.NET.InteropHelpers/NativeContext.cs b/src/Ubiquity.NET.InteropHelpers/NativeContext.cs
new file mode 100644
index 000000000..b703679d7
--- /dev/null
+++ b/src/Ubiquity.NET.InteropHelpers/NativeContext.cs
@@ -0,0 +1,119 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+
+namespace Ubiquity.NET.InteropHelpers
+{
+ // This does NOT use the new C# 14 extension syntax due to several reasons
+ // 1) Code lens does not work https://github.com/dotnet/roslyn/issues/79006 [Sadly marked as "not planned" - e.g., dead-end]
+ // 2) MANY analyzers get things wrong and need to be supressed (CA1000, CA1034, and many others [SAxxxx])
+ // 3) Many tools (like docfx don't support the new syntax yet)
+ // 4) No clear support for Caller* attributes ([CallerArgumentExpression(...)]).
+ //
+ // Bottom line it's a good idea with an incomplete implementation lacking support
+ // in the overall ecosystem. Don't use it unless you absolutely have to until all
+ // of that is sorted out.
+
+ /// Operations and extensions for a Native ABI context
+ ///
+ /// To interop with native ABI callbacks it is important to ensure that any
+ /// context pointer provided is valid when the callback is called by native
+ /// layers. This type provides support for doing so using a .
+ /// The handle is allocated in and then released
+ /// in . The actual target is obtainable via
+ /// which is normally used in the callback to
+ /// get back the original managed object the handle refers to.
+ ///
+ /// Since the handle is allocated the GC will keep it alive until freed. Thus,
+ /// the callback can use the object it gets. Normally,
+ /// is called from a final callback method but it is also possible that the API gurantess
+ /// only a single call to a provided method where it would release the native context
+ /// after materializing it to a managed form. (After materialization, normal GC rules
+ /// apply to the managed instance such that while it is in scope it is not released by
+ /// the GC)
+ ///
+ ///
+ public static class NativeContext
+ {
+ /// Extension method to get a native consumable context pointer for a managed object
+ /// Type of the managed object
+ /// Managed object to get the context for
+ /// Native API consumable "pointer" for a GC handle
+ ///
+ /// This creates a from the instance and gets a native ABI
+ /// pointer from that. The returned value is a native pointer that is convertible
+ /// back to a handle, which can then allow access to the original .
+ ///
+ /// Normally the materialization of a managed target is done via
+ /// in a callback implementation. Ultimately the allocated handle is freed in a call to .
+ ///
+ ///
+ public static unsafe void* AsNativeContext(this T self)
+ {
+ var handle = GCHandle.Alloc( self );
+ try
+ {
+ return GCHandle.ToIntPtr(handle).ToPointer();
+ }
+ catch
+ {
+ handle.Free();
+ throw;
+ }
+ }
+
+ /// Materializes an instance from a native ABI context as a void*
+ /// Type of the managed object to materialize
+ /// Native context
+ /// Materialized value or if not
+ /// if the value is succesfully materialized and if not
+ public static unsafe bool TryFrom(void* ctx, [MaybeNullWhen(false)] out T value)
+ {
+ if(ctx is not null && GCHandle.FromIntPtr( (nint)ctx ).Target is T managedInst)
+ {
+ value = managedInst;
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ /// Releases a native context handle
+ /// Reference to the context to release
+ ///
+ /// This releases the native context AND sets to
+ /// so that no subsequent releases are not possible (or get an exception).
+ ///
+ [SuppressMessage( "Design", "CA1045:Do not pass types by reference", Justification = "Allows controlled reset to null (invalid)" )]
+ public static unsafe void Release(ref void* ctx)
+ {
+ Release(ctx);
+ ctx = null;
+ }
+
+ /// Releases the context without resetting it to
+ /// Context to release
+ ///
+ /// This does NOT re-assign the context as it is intended for use from within a call back
+ /// that performs the release. In such a case any field or property of the containing type
+ /// should be set to to prevent a double free problem. Use of this
+ /// method is discouraged as it requires more care, but is sometimes required.
+ ///
+ public static unsafe void Release(void* ctx)
+ {
+ Debug.Assert(ctx is not null, "Attempting to release a NULL context - something is likely wrong in the caller!");
+
+ // SAFETY - NOP if null, don't trigger a crash in native code.
+ // Debugger assert above will trigger in debug builds.
+ if(ctx is not null)
+ {
+ GCHandle.FromIntPtr((nint)ctx)
+ .Free();
+ }
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.InteropHelpers/Readme.md b/src/Ubiquity.NET.InteropHelpers/Readme.md
index 11fed4774..5adfffb7b 100644
--- a/src/Ubiquity.NET.InteropHelpers/Readme.md
+++ b/src/Ubiquity.NET.InteropHelpers/Readme.md
@@ -1,8 +1,8 @@
# About
-Ubiquity.NET.InteropHelpers helper support common to low level interop libraries. While this
-library is intended to support the Ubiquity.NET.Llvm interop requirements there isn't anything
-bound to that library in the support here. That is it is independent and a useful library for
-any code base providing interop support.
+Ubiquity.NET.InteropHelpers helper support common to low level interop libraries. While
+this library is intended to support the Ubiquity.NET.Llvm interop requirements there isn't
+anything bound to that library in the support here. That is it is independent and a useful
+library for any code base providing interop support.
# Key Features
* String handling
@@ -18,10 +18,10 @@ any code base providing interop support.
* Delegates and NativeCallbacks as Function pointers
* Function pointers are a new feature of C# that makes for very high performance interop
scenarios. However, sometimes the callback for a function pointer actually needs
- additional data not part of the parameters of the function to work properly. This library
- provides support for such scenarios where a delegate is used to "capture" the data while
- still supporting AOT scenarios. (NOTE: Marshal.GetFunctionPointerForDelegate() must
- dynamically emit a thunk that contains the proper signature and the captured "this"
- pointer so is NOT AOT friendly) The support offered in this library, though a bit more
- tedious, is AOT friendly.
+ additional data not part of the parameters of the function to work properly. This
+ library provides support for such scenarios where a delegate is used to "capture" the
+ data while still supporting AOT scenarios. (NOTE: Marshal.GetFunctionPointerForDelegate()
+ must dynamically emit a thunk that contains the proper signature and the captured
+ "this" pointer so is NOT AOT friendly) The support offered in this library, though a
+ bit more tedious, is AOT friendly.
diff --git a/src/Ubiquity.NET.InteropHelpers/SafeGCHandle.cs b/src/Ubiquity.NET.InteropHelpers/SafeGCHandle.cs
deleted file mode 100644
index 038b40882..000000000
--- a/src/Ubiquity.NET.InteropHelpers/SafeGCHandle.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
-// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
-
-using System.Runtime.InteropServices;
-
-namespace Ubiquity.NET.InteropHelpers
-{
- /// Helper type to wrap GC handles with ref counting
- ///
- /// Instances of this class (as a private member of a controlled type) help
- /// to ensure the referenced object stays alive while in use for native ABI as a
- /// "context" for callbacks. This is the key to ref counted behavior to hold 'o'
- /// (and anything it references) alive for the GC. The "ownership" of the refcount
- /// is handed to native code while the calling code is free to no longer reference
- /// the containing instance as it holds an allocated GCHandle for itself and THAT
- /// is kept alive by a ref count that is "owned" by native code.
- ///
- /// This is used as a member of such a holding type so that 'AddRefAndGetNativeContext'
- /// retrieves a marshaled GCHandle for the containing/Controlling instance that is
- /// then provided as the native "Context" parameter (usually as a void*).
- ///
- /// It is assumed that instances of this type are held in a disposable type such
- /// that when the containing type is disposed, then this is disposed. Additionally,
- /// this assumes that the native code MIGHT dispose of this instance and that callers
- /// should otherwise account for the ref count increase to hold the instance alive. That
- /// is, by holding a to self, with an AddRef'd handle the instance
- /// would live until the app is terminated! Thus applications using this MUST understand
- /// the native code use and account for the disposable of any instances with this as a
- /// member. Typically a callback provided to the native code is used to indicate release
- /// of the resource. That callback will call dispose to decrement the refcount on the
- /// handle. If the ref count lands at 0, then the object it refers to is subject to
- /// normal GC.
- ///
- public sealed class SafeGCHandle
- : SafeHandle
- {
- /// Initializes a new instance of the class.
- /// Object to allocate a GCHandle for that is controlled by this instance
- ///
- /// It is expected that the type of has this
- /// as a private member so that it controls the lifetime of it's container.
- ///
- public SafeGCHandle( object o )
- : base( 0, ownsHandle: true )
- {
- handle = (nint)GCHandle.Alloc( o );
- }
-
- ///
- public override bool IsInvalid => handle == 0;
-
- /// Adds a ref count to this handle AND converts the allocated for use in native code
- /// context value for use in native code to refer to the held by this instance
- ///
- /// A native call back that receives the returned context can reconstitute the via
- /// and from that it can get the original instance the handle refers to via
- ///
- public unsafe nint AddRefAndGetNativeContext( )
- {
- bool ignoredButRequired = false;
- DangerousAddRef( ref ignoredButRequired );
- return handle;
- }
-
- ///
- protected override bool ReleaseHandle( )
- {
- GCHandle.FromIntPtr( handle ).Free();
- return true;
- }
- }
-}
diff --git a/src/Ubiquity.NET.Llvm.JIT.Tests/OrcJitTests.cs b/src/Ubiquity.NET.Llvm.JIT.Tests/OrcJitTests.cs
index dd926fe61..09f63cabf 100644
--- a/src/Ubiquity.NET.Llvm.JIT.Tests/OrcJitTests.cs
+++ b/src/Ubiquity.NET.Llvm.JIT.Tests/OrcJitTests.cs
@@ -3,7 +3,6 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
diff --git a/src/Ubiquity.NET.Llvm/Context.cs b/src/Ubiquity.NET.Llvm/Context.cs
index d85dd8b4f..48044f774 100644
--- a/src/Ubiquity.NET.Llvm/Context.cs
+++ b/src/Ubiquity.NET.Llvm/Context.cs
@@ -263,6 +263,17 @@ public void Dispose( )
{
if(Handle != nint.Zero)
{
+ // remove any diagnostic handler so it the callback's context doesn't leak
+ unsafe
+ {
+ void* context = LLVMContextGetDiagnosticContext(Handle);
+ if(context is not null)
+ {
+ LLVMContextSetDiagnosticHandler(Handle, null, null);
+ NativeContext.Release(context);
+ }
+ }
+
Handle.Dispose();
InvalidateAfterMove();
}
diff --git a/src/Ubiquity.NET.Llvm/ContextAlias.cs b/src/Ubiquity.NET.Llvm/ContextAlias.cs
index 775e4149e..48a4c22ca 100644
--- a/src/Ubiquity.NET.Llvm/ContextAlias.cs
+++ b/src/Ubiquity.NET.Llvm/ContextAlias.cs
@@ -144,10 +144,19 @@ public AttributeValue CreateAttribute( LazyEncodedString name, LazyEncodedString
public void SetDiagnosticHandler( DiagnosticInfoCallbackAction handler )
{
- using var callBack = new DiagnosticCallbackHolder(handler);
unsafe
{
- LLVMContextSetDiagnosticHandler( Handle, &DiagnosticCallbackHolder.DiagnosticHandler, callBack.AddRefAndGetNativeContext() );
+ void* ctx = null;
+ try
+ {
+ ctx = handler.AsNativeContext();
+ LLVMContextSetDiagnosticHandler( Handle, &DiagnosticCallbacks.DiagnosticHandler, ctx );
+ }
+ catch when (ctx is not null)
+ {
+ NativeContext.Release(ctx);
+ throw;
+ }
}
}
diff --git a/src/Ubiquity.NET.Llvm/DiagnosticCallbackHolder.cs b/src/Ubiquity.NET.Llvm/DiagnosticCallbacks.cs
similarity index 59%
rename from src/Ubiquity.NET.Llvm/DiagnosticCallbackHolder.cs
rename to src/Ubiquity.NET.Llvm/DiagnosticCallbacks.cs
index d796297e3..81d4b9cf4 100644
--- a/src/Ubiquity.NET.Llvm/DiagnosticCallbackHolder.cs
+++ b/src/Ubiquity.NET.Llvm/DiagnosticCallbacks.cs
@@ -18,54 +18,31 @@ namespace Ubiquity.NET.Llvm
///
public delegate void DiagnosticInfoCallbackAction( DiagnosticInfo info );
- internal sealed class DiagnosticCallbackHolder
- : IDisposable
+ // native callbacks for an LLVM context Diagnostic messages.
+ internal static class DiagnosticCallbacks
{
- public DiagnosticCallbackHolder( DiagnosticInfoCallbackAction diagnosticHandler )
- {
- Delegate = diagnosticHandler;
- AllocatedSelf = new( this );
- }
-
- public void Dispose( )
- {
- if(!AllocatedSelf.IsInvalid && !AllocatedSelf.IsClosed)
- {
- // Decrements the ref count on the handle
- // might not actually destroy anything
- AllocatedSelf.Dispose();
- }
- }
-
- internal unsafe nint AddRefAndGetNativeContext( )
- {
- return AllocatedSelf.AddRefAndGetNativeContext();
- }
-
[UnmanagedCallersOnly( CallConvs = [ typeof( CallConvCdecl ) ] )]
[SuppressMessage( "Design", "CA1031:Do not catch general exception types", Justification = "REQUIRED for unmanaged callback - Managed exceptions must never cross the boundary to native code" )]
internal static unsafe void DiagnosticHandler( nint abiInfo, void* context )
{
try
{
- if(MarshalGCHandle.TryGet( context, out DiagnosticCallbackHolder? self ))
+ if(NativeContext.TryFrom(context, out var self ))
{
- self.Delegate( new( abiInfo ) );
+ self( new( abiInfo ) );
}
}
catch
{
- // stop in debugger as this is a detected app error.
+ // SAFETY: stop in debugger as this is a detected app error.
// Test for attached debugger directly to avoid prompts, WER cruft etc...
- // End user should NOT be prompted to attach a debugger!
+ // End user should NOT be prompted to attach a debugger, if one is already
+ // attached then stop as the resulting exception is likely an app crash!
if(Debugger.IsAttached)
{
Debugger.Break();
}
}
}
-
- private readonly DiagnosticInfoCallbackAction Delegate;
- private readonly SafeGCHandle AllocatedSelf;
}
}
diff --git a/src/Ubiquity.NET.Llvm/DisAssembler.cs b/src/Ubiquity.NET.Llvm/DisAssembler.cs
index 407e0f7e5..da5234656 100644
--- a/src/Ubiquity.NET.Llvm/DisAssembler.cs
+++ b/src/Ubiquity.NET.Llvm/DisAssembler.cs
@@ -1,9 +1,6 @@
// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
-// this file declares and uses the "experimental" interface `IDisassemblerCallbacks`.
-#pragma warning disable LLVM002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
-
using static Ubiquity.NET.Llvm.Interop.ABI.llvm_c.Disassembler;
namespace Ubiquity.NET.Llvm
@@ -18,8 +15,8 @@ namespace Ubiquity.NET.Llvm
/// but also applies to the
/// `TagBuf` parameter of
///
- [SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Closely related only used here" )]
- [Experimental( "LLVM002" )]
+ [SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Closely related; only used here" )]
+ [Experimental( "LLVMEXP003" )]
public interface IDisassemblerCallbacks
{
/// Purpose not fully known or well explained in LLVM docs
@@ -69,11 +66,22 @@ public enum DisassemblerOptions
}
/// LLVM Disassembler
+ [Experimental( "LLVMEXP003" )]
public sealed class Disassembler
: IDisposable
{
///
- public void Dispose( ) => Handle.Dispose();
+ public void Dispose( )
+ {
+ Handle.Dispose();
+ unsafe
+ {
+ if(CallbacksHandle is not null)
+ {
+ NativeContext.Release(ref CallbacksHandle);
+ }
+ }
+ }
/// Initializes a new instance of the class.
/// Triple for the instruction set to disassemble
@@ -121,16 +129,24 @@ public Disassembler( Triple triple
unsafe
{
ArgumentNullException.ThrowIfNull( triple );
- CallBacksHandle = callBacks is null ? null : GCHandle.Alloc( callBacks );
- Handle = LLVMCreateDisasmCPUFeatures(
- triple.ToString() ?? LazyEncodedString.Empty,
- cpu,
- features,
- CallBacksHandle.HasValue ? GCHandle.ToIntPtr( CallBacksHandle.Value ).ToPointer() : null,
- tagType,
- CallBacksHandle.HasValue ? &NativeInfoCallBack : null,
- CallBacksHandle.HasValue ? &NativeSymbolLookupCallback : null
- );
+ CallbacksHandle = callBacks is null ? null : callBacks.AsNativeContext();
+ try
+ {
+ Handle = LLVMCreateDisasmCPUFeatures(
+ triple.ToString() ?? LazyEncodedString.Empty,
+ cpu,
+ features,
+ CallbacksHandle, // The context provided to callbacks
+ tagType,
+ CallbacksHandle is null ? null : &NativeInfoCallBack,
+ CallbacksHandle is null ? null : &NativeSymbolLookupCallback
+ );
+ }
+ catch when (CallbacksHandle is not null)
+ {
+ NativeContext.Release(ref CallbacksHandle);
+ throw;
+ }
}
}
@@ -167,7 +183,7 @@ public bool SetOptions( DisassemblerOptions options )
}
}
- private readonly GCHandle? CallBacksHandle;
+ private unsafe void* CallbacksHandle;
private readonly LLVMDisasmContextRef Handle;
#region Native marshalling callbacks
@@ -184,7 +200,7 @@ public bool SetOptions( DisassemblerOptions options )
{
try
{
- if(!MarshalGCHandle.TryGet( disInfo, out IDisassemblerCallbacks? callBacks ))
+ if(!NativeContext.TryFrom(disInfo, out var callBacks ))
{
return null;
}
@@ -223,7 +239,7 @@ private static unsafe int NativeInfoCallBack( void* disInfo, UInt64 pc, UInt64 o
{
try
{
- return MarshalGCHandle.TryGet( disInfo, out IDisassemblerCallbacks? callBacks )
+ return NativeContext.TryFrom(disInfo, out var callBacks )
? callBacks.OpInfo( pc, offset, opSize, instSize, tagType, (nint)tagBuf )
: 0; // TODO: Is this a legit failure return value?
}
diff --git a/src/Ubiquity.NET.Llvm/GlobalNamespaceImports.cs b/src/Ubiquity.NET.Llvm/GlobalNamespaceImports.cs
index 69593005b..ff5ec5c6a 100644
--- a/src/Ubiquity.NET.Llvm/GlobalNamespaceImports.cs
+++ b/src/Ubiquity.NET.Llvm/GlobalNamespaceImports.cs
@@ -20,6 +20,7 @@ where it belongs.
global using System.Buffers;
global using System.Collections;
global using System.Collections.Generic;
+global using System.Collections.Immutable;
global using System.Collections.ObjectModel;
global using System.Diagnostics;
global using System.Diagnostics.CodeAnalysis;
diff --git a/src/Ubiquity.NET.Llvm/ILibLLVM.cs b/src/Ubiquity.NET.Llvm/ILibLLVM.cs
index 38f8a6c46..129f31a95 100644
--- a/src/Ubiquity.NET.Llvm/ILibLLVM.cs
+++ b/src/Ubiquity.NET.Llvm/ILibLLVM.cs
@@ -2,8 +2,6 @@
// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
// This is cribbed from the interop library to prevent the need for applications to take a direct dependency on the interop library
-using System.Collections.Immutable;
-
namespace Ubiquity.NET.Llvm
{
/// Code gen target to register/initialize
diff --git a/src/Ubiquity.NET.Llvm/IOperandCollection.cs b/src/Ubiquity.NET.Llvm/IOperandCollection.cs
index c657756bc..cf2de5c3c 100644
--- a/src/Ubiquity.NET.Llvm/IOperandCollection.cs
+++ b/src/Ubiquity.NET.Llvm/IOperandCollection.cs
@@ -29,7 +29,6 @@ public interface IOperandCollection
/// Inclusive start index for the slice
/// Exclusive end index for the slice
/// Slice of the collection
- [SuppressMessage( "Naming", "CA1716:Identifiers should not match keywords", Justification = "Naming is consistent with System.Range parameters" )]
IOperandCollection Slice( int start, int end )
{
return new OperandCollectionSlice( this, new Range( start, end ) );
diff --git a/src/Ubiquity.NET.Llvm/Instructions/InstructionExtensions.cs b/src/Ubiquity.NET.Llvm/Instructions/InstructionExtensions.cs
index a4bdaa210..161b0792f 100644
--- a/src/Ubiquity.NET.Llvm/Instructions/InstructionExtensions.cs
+++ b/src/Ubiquity.NET.Llvm/Instructions/InstructionExtensions.cs
@@ -3,6 +3,16 @@
namespace Ubiquity.NET.Llvm.Instructions
{
+ // This does NOT use the new C# 14 extension syntax due to several reasons
+ // 1) Code lens does not work https://github.com/dotnet/roslyn/issues/79006 [Sadly marked as "not planned" - e.g., dead-end]
+ // 2) MANY analyzers get things wrong and need to be supressed (CA1000, CA1034, and many others [SAxxxx])
+ // 3) Many tools (like docfx don't support the new syntax yet)
+ // 4) No clear support for Caller* attributes ([CallerArgumentExpression(...)]).
+ //
+ // Bottom line it's a good idea with an incomplete implementation lacking support
+ // in the overall ecosystem. Don't use it unless you absolutely have to until all
+ // of that is sorted out.
+
/// Provides extension methods to that cannot be achieved as members of the class
///
/// Using generic static extension methods allows for fluent coding while retaining the type of the "this" parameter.
diff --git a/src/Ubiquity.NET.Llvm/Library.cs b/src/Ubiquity.NET.Llvm/Library.cs
index 80f2a90c4..2ed8905f9 100644
--- a/src/Ubiquity.NET.Llvm/Library.cs
+++ b/src/Ubiquity.NET.Llvm/Library.cs
@@ -7,8 +7,6 @@ of this library from direct dependencies on the interop library. If a consumer h
the low level interop (Test code sometimes does) it must explicitly reference it.
*/
-using System.Collections.Immutable;
-
using static Ubiquity.NET.Llvm.Interop.ABI.libllvm_c.AttributeBindings;
using static Ubiquity.NET.Llvm.Interop.ABI.llvm_c.DebugInfo;
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/CustomMaterializationUnit.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/CustomMaterializationUnit.cs
index d42ae0212..039a9d2f0 100644
--- a/src/Ubiquity.NET.Llvm/OrcJITv2/CustomMaterializationUnit.cs
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/CustomMaterializationUnit.cs
@@ -1,8 +1,6 @@
// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
-using System.Collections.Immutable;
-
using static Ubiquity.NET.Llvm.Interop.ABI.llvm_c.Orc;
namespace Ubiquity.NET.Llvm.OrcJITv2
@@ -10,8 +8,8 @@ namespace Ubiquity.NET.Llvm.OrcJITv2
/// Delegate to perform action on Materialization
/// that serves as the context for this materialization
///
- /// This must be a "custom" delegate as the is a
- /// ref type that is NOT allowed as a type parameter for .
+ /// This is "custom" delegate for consistency with
+ /// which requires one for .NET runtimes lower than .NET 9.
///
public delegate void MaterializationAction( MaterializationResponsibility r );
@@ -22,7 +20,7 @@ namespace Ubiquity.NET.Llvm.OrcJITv2
/// This must be a "custom" delegate as the is a
/// ref type that is NOT allowed as a type parameter for .
///
- public delegate void DiscardAction( JITDyLib jitLib, SymbolStringPoolEntry symbol );
+ public delegate void DiscardAction( ref readonly JITDyLib jitLib, SymbolStringPoolEntry symbol );
/// LLVM ORC JIT v2 custom materialization unit
///
@@ -91,48 +89,47 @@ private static LLVMOrcMaterializationUnitRef MakeHandle(
ValidateInitSym(initSymbol, symbols);
LLVMOrcMaterializationUnitRef retVal;
- // This will internally manage the lifetime
-#pragma warning disable IDE0063 // Use simple 'using' statement
- using( var materializer = new CustomMaterializer(materializeAction, discardAction))
+ using IMemoryOwner nativeSyms = symbols.InitializeNativeCopy();
+
+ // using expression ensures cleanup of addref in case of exceptions...
+ // But since it is an immutable Value type that isn't viable here and try/finally is used
+ LLVMOrcSymbolStringPoolEntryRef nativeInitSym = initSymbol?.AddRefForNative() ?? default;
+ unsafe
{
- using(IMemoryOwner nativeSyms = symbols.InitializeNativeCopy())
+ void* nativeContext = null; // init only from inside try/catch block
+ try
{
- nint nativeContext = materializer.AddRefAndGetNativeContext();
-
- // using expression ensures cleanup of addref in case of exceptions...
- // But since it is an immutable Value type that isn't viable here and try/finally is used
- LLVMOrcSymbolStringPoolEntryRef nativeInitSym = initSymbol?.AddRefForNative() ?? default;
- try
- {
- unsafe
- {
- using var pinnedSyms = nativeSyms.Memory.Pin();
- retVal = LLVMOrcCreateCustomMaterializationUnit(
- name,
- (void*)nativeContext,
- (LLVMOrcCSymbolFlagsMapPair*)pinnedSyms.Pointer,
- checked((nuint)symbols.Length),
- nativeInitSym,
- &NativeCallbacks.Materialize,
- materializer.SupportsDiscard ? &NativeCallbacks.Discard : null,
- &NativeCallbacks.Destroy
- );
-
- // ownership of this symbol was moved to native, mark transfer so auto clean up (for exceptional cases)
- // does NOT kick in.
- nativeInitSym = default;
- }
- }
- finally
+ nativeContext = new MaterializerCallbacksHolder(materializeAction, discardAction).AsNativeContext();
+ using var pinnedSyms = nativeSyms.Memory.Pin();
+ retVal = LLVMOrcCreateCustomMaterializationUnit(
+ name,
+ nativeContext,
+ (LLVMOrcCSymbolFlagsMapPair*)pinnedSyms.Pointer,
+ checked((nuint)symbols.Length),
+ nativeInitSym,
+ &NativeCallbacks.Materialize,
+ &NativeCallbacks.Discard,
+ &NativeCallbacks.Destroy
+ );
+
+ // ownership of this symbol was moved to native, mark transfer so auto clean up (for exceptional cases)
+ // does NOT kick in.
+ nativeInitSym = default;
+ }
+ catch when (nativeContext is not null)
+ {
+ // release the handle allocated for the native code as it isn't used there in the face of an exception here
+ NativeContext.Release( ref nativeContext);
+ throw;
+ }
+ finally
+ {
+ if(!nativeInitSym.IsNull)
{
- if(!nativeInitSym.IsNull)
- {
- nativeInitSym.Dispose();
- }
+ nativeInitSym.Dispose();
}
}
}
-#pragma warning restore IDE0063 // Use simple 'using' statement
return retVal;
}
@@ -159,22 +156,18 @@ internal static unsafe void Materialize( void* context, /*LLVMOrcMaterialization
{
try
{
- if(MarshalGCHandle.TryGet( context, out CustomMaterializer? self ))
+ if(NativeContext.TryFrom(context, out var self ))
{
+ // Destroy callback is NOT called if this one is...
+ // Internally LLVM will set the context to null [Undocumented!]
+ NativeContext.Release(context);
+
#pragma warning disable CA2000 // Dispose objects before losing scope
#pragma warning disable IDISP004 // Don't ignore created IDisposable
// [It is an alias; Dispose is a NOP with wasted overhead]
- self.MaterializeHandler( new MaterializationResponsibility( abiResponsibility, alias: true ) );
+ self.Materialize( new MaterializationResponsibility( abiResponsibility, alias: true ) );
#pragma warning restore IDISP004 // Don't ignore created IDisposable
#pragma warning restore CA2000 // Dispose objects before losing scope
-
-#pragma warning disable IDISP007 // Don't dispose injected
- /*
- Not really "injected" and this is how the data/context is disposed when the
- native code is done with it.
- */
- self.Dispose();
-#pragma warning restore IDISP007 // Don't dispose injected
}
}
catch
@@ -189,12 +182,13 @@ internal static unsafe void Discard( void* context, /*LLVMOrcJITDylibRef*/ nint
{
try
{
- if(MarshalGCHandle.TryGet( context, out CustomMaterializer? self ))
+ if(NativeContext.TryFrom( context, out var self ))
{
#pragma warning disable CA2000 // Dispose objects before losing scope
#pragma warning disable IDISP004 // Don't ignore created IDisposable
// [It is an alias; Dispose is a NOP with wasted overhead]
- self.DiscardHandler?.Invoke( new JITDyLib( abiLib ), new SymbolStringPoolEntry( abiSymbol, alias: true ) );
+ var managedLib = new JITDyLib( abiLib );
+ self.Discard( in managedLib, new SymbolStringPoolEntry( abiSymbol, alias: true ) );
#pragma warning restore IDISP004 // Don't ignore created IDisposable
#pragma warning restore CA2000 // Dispose objects before losing scope
}
@@ -211,15 +205,13 @@ internal static unsafe void Destroy( void* context )
{
try
{
- if(MarshalGCHandle.TryGet( context, out CustomMaterializer? self ))
+ if(NativeContext.TryFrom(context, out var self ))
{
-#pragma warning disable IDISP007 // Don't dispose injected
- /*
- Not really "injected" and this is how the data/context is disposed when the
- native code is done with it.
- */
- self.Dispose();
-#pragma warning restore IDISP007 // Don't dispose injected
+ // self is a managed instance with normal GC rules now so release
+ // the context created for callbacks as it is not needed anymore.
+ // After this scope exits, GC is free to collect the instance.
+ NativeContext.Release(context);
+ self.Destroy();
}
}
catch
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/CustomMaterializer.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/CustomMaterializer.cs
deleted file mode 100644
index 4866ad212..000000000
--- a/src/Ubiquity.NET.Llvm/OrcJITv2/CustomMaterializer.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
-// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
-
-namespace Ubiquity.NET.Llvm.OrcJITv2
-{
- /// Holds delegates for performing custom materialization for a single materialization unit
- internal sealed class CustomMaterializer
- : IDisposable
- {
- /// Initializes a new instance of the class.
- /// Action to perform to materialize the symbol
- /// Action to perform when the JIT discards/replaces a symbol
- public CustomMaterializer( MaterializationAction materializeAction, DiscardAction? discardAction )
- {
- AllocatedSelf = new( this );
- MaterializeHandler = materializeAction;
- DiscardHandler = discardAction;
- }
-
- ///
- public void Dispose( )
- {
- if(!AllocatedSelf.IsInvalid && !AllocatedSelf.IsClosed)
- {
- // Decrements the ref count on the handle
- // might not actually destroy anything
- AllocatedSelf.Dispose();
- }
- }
-
- internal bool SupportsDiscard => DiscardHandler is not null;
-
- internal unsafe nint AddRefAndGetNativeContext( )
- {
- return AllocatedSelf.AddRefAndGetNativeContext();
- }
-
- internal MaterializationAction MaterializeHandler { get; init; }
-
- internal DiscardAction? DiscardHandler { get; init; }
-
- // This is the key to ref counted behavior to hold this instance (and anything it references)
- // alive for the GC. The "ownership" of the refcount is handed to native code while the
- // calling code is free to no longer reference this instance as it holds an allocated
- // GCHandle for itself and THAT is kept alive by a ref count that is "owned" by native code.
- private SafeGCHandle AllocatedSelf { get; init; }
- }
-}
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/ExecutionSession.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/ExecutionSession.cs
index ba10381f2..9e5aa3e1e 100644
--- a/src/Ubiquity.NET.Llvm/OrcJITv2/ExecutionSession.cs
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/ExecutionSession.cs
@@ -54,7 +54,7 @@ out LLVMOrcLazyCallThroughManagerRef resultHandle
}
/*
- [Experimental]
+ [Experimental("????")]
public void Lookup(LookupKind kind, scoped ReadOnlySpan order, scoped ReadOnlySpan symbols, LookupResultHandler handler)
{
// validate args...
@@ -264,6 +264,7 @@ internal ExecutionSession( nint h )
internal LLVMOrcExecutionSessionRef Handle { get; init; }
+#if FUTURE_DEVELOPMENT_AREA
/// Native code callback for error reporting
/// The context for the callback is a with a target of
/// ABI handle for an error ref
@@ -292,5 +293,6 @@ private static unsafe void NativeErrorReporterCallback( void* context, /*LLVMErr
Debug.Assert( false, "Exception in native callback!" );
}
}
+#endif
}
}
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/GlobalMemoryAllocatorBase.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/GlobalMemoryAllocatorBase.cs
new file mode 100644
index 000000000..f886ac9f2
--- /dev/null
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/GlobalMemoryAllocatorBase.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+using static Ubiquity.NET.Llvm.Interop.ABI.llvm_c.OrcEE;
+
+namespace Ubiquity.NET.Llvm.OrcJITv2
+{
+ /// Base class for a Global allocator for use as an Object layer in OrcJITv2
+ ///
+ /// Instances of this are provided as an object layer via an implementation of .
+ /// While this type implements via it is ONLY intended for
+ /// clean up on exceptions. A factory returns the instances to the native JIT, which takes over ownership.
+ ///
+ /// Derived types need not be concrend with any of the low level native interop. Instead
+ /// they simply implement the abstract methods to perform the required allocations. This
+ /// base type handles all of the interop, including the reverse P/Invoke marhsalling for
+ /// callbacks.
+ ///
+ ///
+ [Experimental("LLVMEXP004")]
+ public abstract class GlobalMemoryAllocatorBase
+ : ObjectLayer
+ , IJitMemoryAllocator
+ {
+ /// Initializes a new instance of the class.
+ /// Session for this object layer
+ protected GlobalMemoryAllocatorBase(ExecutionSession session)
+ : base()
+ {
+ unsafe
+ {
+ CallbackContext = this.AsNativeContext();
+ Handle = LLVMOrcCreateRTDyldObjectLinkingLayerWithMCJITMemoryManagerLikeCallbacks(
+ session.Handle,
+ CallbackContext,
+ &MemoryAllocatorNativeCallbacks.CreatePerObjContextAsGlobalContext,
+ &MemoryAllocatorNativeCallbacks.NotifyTerminating, // Releases the global context
+ &MemoryAllocatorNativeCallbacks.AllocateCodeSection,
+ &MemoryAllocatorNativeCallbacks.AllocateDataSection,
+ &MemoryAllocatorNativeCallbacks.FinalizeMemory,
+ &MemoryAllocatorNativeCallbacks.DestroyPerObjContextNOP // NOP
+ );
+ }
+ }
+
+ ///
+ public abstract nuint AllocateCodeSection(nuint size, UInt32 alignment, UInt32 sectionId, LazyEncodedString sectionName );
+
+ ///
+ public abstract nuint AllocateDataSection(nuint size, UInt32 alignment, UInt32 sectionId, LazyEncodedString sectionName, bool isReadOnly);
+
+ ///
+ public abstract bool FinalizeMemory([NotNullWhen(false)] out LazyEncodedString? errMsg);
+
+ ///
+ public virtual void ReleaseContext()
+ {
+ unsafe
+ {
+ NativeContext.Release(ref CallbackContext);
+ }
+ }
+
+ //protected override void Dispose( bool disposing )
+ //{
+ // Do NOT overload this, the base will dispose the object layer ReleaseContext() is called to release the call back context
+ //}
+
+ private unsafe void* CallbackContext;
+ }
+}
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/IJitMemoryAllocator.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/IJitMemoryAllocator.cs
new file mode 100644
index 000000000..a935d48ea
--- /dev/null
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/IJitMemoryAllocator.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+#pragma warning disable SA1649 // Filename must match type name
+#pragma warning disable SA1600 // Elements must be documented
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 'Type_or_Member'
+
+namespace Ubiquity.NET.Llvm.OrcJITv2
+{
+ public interface IJitMemoryAllocator
+ {
+ /// Allocate a block of contiguous memory for use as code execution by the native code JIT engine
+ /// Size of the block
+ /// alignment requirements of the block
+ /// ID for the section
+ /// Name of the section
+ /// Address of the first byte of the allocated memory
+ ///
+ /// If the memory is allocated from the managed heap then the returned address MUST
+ /// remain pinned until is called on this allocator
+ ///
+ /// The Execute only page setting and any other page properties is not applied to the returned
+ /// address (or entire memory of the allocated section) until is called.
+ /// This allows the JIT to write code into the memory area even if it is ultimately Execute-Only.
+ ///
+ ///
+ nuint AllocateCodeSection(nuint size, UInt32 alignment, UInt32 sectionId, LazyEncodedString sectionName );
+
+ /// Allocate a block of contiguous memory for use as data by the native code JIT engine
+ /// Size of the block
+ /// alignment requirements of the block
+ /// ID for the section
+ /// Name of the section
+ /// Memory section is Read-Only
+ /// Address of the first byte of the allocated memory
+ ///
+ /// If the memory is allocated from the managed heap then the returned address MUST
+ /// remain pinned until is called on this allocator.
+ ///
+ /// The and any other page properties is not applied to the returned
+ /// address (or entire memory of the allocated section) until is called.
+ /// This allows the JIT to write initial data into the memory even if it is ultimately Read-Only.
+ ///
+ ///
+ nuint AllocateDataSection(nuint size, UInt32 alignment, UInt32 sectionId, LazyEncodedString sectionName, bool isReadOnly);
+
+ /// Finalizes a previous allocation by applying page settings for the allocation
+ /// Error message in the event of a failure
+ /// if successfull ( is ); if not ( has the reason)
+ bool FinalizeMemory([NotNullWhen(false)] out LazyEncodedString? errMsg);
+
+ /// Release the context for the memory. No further callbacks will occur for this allocator
+ ///
+ /// This is similar to a call to except that it releases only the native context
+ /// in respnse to a callback, not the handle for allocator itself. That MUST live longer than the JIT as any memory
+ /// it allocated MAY still be in use as code or data in the JIT. (This interface only deals with WHOLE JIT memory
+ /// allocation. It is at least plausible to have an allocator per JitDyLib but that would end up needing to leverage
+ /// a global one to ensure that secion ordering and size limits of the underlying OS are met. If such a things is
+ /// ever implented, it would use a different interface for clarity.)
+ ///
+ void ReleaseContext();
+ }
+}
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/IMaterializerCallbacks.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/IMaterializerCallbacks.cs
new file mode 100644
index 000000000..d11c72caa
--- /dev/null
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/IMaterializerCallbacks.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+namespace Ubiquity.NET.Llvm.OrcJITv2
+{
+ /// Interface for a type that receives call backs for materialization (usually a type derived from )
+ public interface IMaterializerCallbacks
+ {
+ /// Materializes all symbols in this unit except those that were previously discarded
+ /// that serves as the context for this materialization
+ void Materialize( MaterializationResponsibility r );
+
+ /// Discards a symbol overwridden by the JIT (Before materialization)
+ /// Library the symbols is discarded from
+ /// Symbol being discarded
+ void Discard( ref readonly JITDyLib jitLib, SymbolStringPoolEntry symbol );
+
+ /// Destroys the materializer callback context
+ public void Destroy();
+ }
+}
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/IrTransformLayer.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/IrTransformLayer.cs
index 6df80b4a6..dbf7dd2cd 100644
--- a/src/Ubiquity.NET.Llvm/OrcJITv2/IrTransformLayer.cs
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/IrTransformLayer.cs
@@ -38,13 +38,21 @@ public void Emit( MaterializationResponsibility r, ThreadSafeModule tsm )
/// Action to perform that transforms modules materialized in a JIT
public void SetTransform( TransformAction transformAction )
{
- // Create a holder for the action; the Dispose will take care of things
- // in the event of an exception and will become a NOP if transfer of
- // ownership completes.
- using var holder = new TransformCallback(transformAction);
unsafe
{
- LLVMOrcIRTransformLayerSetTransform( Handle, TransformCallback.Callback, (void*)holder.AddRefAndGetNativeContext() );
+ // Create a holder for the action; the try/catch will take care of things
+ // in the event of an exception and will be a NOP if transfer of ownership
+ // completes.
+ void* ctx = transformAction.AsNativeContext();
+ try
+ {
+ LLVMOrcIRTransformLayerSetTransform( Handle, &TransformCallback.Transform, ctx );
+ }
+ catch when (ctx is not null)
+ {
+ NativeContext.Release(ref ctx);
+ throw;
+ }
}
}
@@ -57,33 +65,10 @@ internal IrTransformLayer( LLVMOrcIRTransformLayerRef h )
// internal keep alive holder for a native call back as a delegate
private sealed class TransformCallback
- : IDisposable
{
- public TransformCallback( TransformAction transformAction )
- {
- AllocatedSelf = new( this );
- TransformAction = transformAction;
- }
-
- public void Dispose( )
- {
- AllocatedSelf.Dispose();
- }
-
- internal TransformAction TransformAction { get; }
-
- internal unsafe nint AddRefAndGetNativeContext( )
- {
- return AllocatedSelf.AddRefAndGetNativeContext();
- }
-
- private readonly SafeGCHandle AllocatedSelf;
-
- internal static unsafe delegate* unmanaged[Cdecl]< void*, nint*, nint, nint > Callback => &Transform;
-
[UnmanagedCallersOnly( CallConvs = [ typeof( CallConvCdecl ) ] )]
[SuppressMessage( "Design", "CA1031:Do not catch general exception types", Justification = "REQUIRED for unmanaged callback - Managed exceptions must never cross the boundary to native code" )]
- private static unsafe /*LLVMErrorRef*/ nint Transform(
+ internal static unsafe /*LLVMErrorRef*/ nint Transform(
void* context,
/*LLVMOrcThreadSafeModuleRef* */nint* modInOut,
/*LLVMOrcMaterializationResponsibilityRef*/ nint resp
@@ -98,7 +83,7 @@ internal unsafe nint AddRefAndGetNativeContext( )
try
{
- if(!MarshalGCHandle.TryGet( context, out TransformCallback? self ))
+ if(!NativeContext.TryFrom(context, out var self ))
{
return LLVMErrorRef.CreateForNativeOut( "Internal Error: Invalid context provided for native callback"u8 );
}
@@ -112,7 +97,7 @@ internal unsafe nint AddRefAndGetNativeContext( )
// if replaceMode is not null then it is moved to the native caller as an "out" param
// Dispose, even if NOP, is just wasted overhead.
- self.TransformAction( tsm, responsibility, out ThreadSafeModule? replacedMod );
+ self( tsm, responsibility, out ThreadSafeModule? replacedMod );
#pragma warning restore CA2000 // Dispose objects before losing scope
#pragma warning restore IDISP001 // Dispose created
if(replacedMod is not null)
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/LLJitBuilder.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/LLJitBuilder.cs
index 61dcc7f12..c3055f6fd 100644
--- a/src/Ubiquity.NET.Llvm/OrcJITv2/LLJitBuilder.cs
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/LLJitBuilder.cs
@@ -30,9 +30,22 @@ public LLJitBuilder( TargetMachineBuilder builder )
public void Dispose( )
{
// ensure move semantics work by making this Idempotent.
+ // If handle is null this instance does not own it (or the context)
if(!Handle.IsNull)
{
Handle.Dispose();
+ unsafe
+ {
+ if(ObjectLinkingLayerContext is not null)
+ {
+ // If the callback context handle exists and is allocated then free it now.
+ // The handle for this builder itself is already closed so LLVM should not
+ // have any callbacks for this to handle... (docs are silent on the point)
+ NativeContext.Release(ObjectLinkingLayerContext);
+ ObjectLinkingLayerContext = null;
+ }
+ }
+
InvalidateAfterMove();
}
}
@@ -57,14 +70,14 @@ public void SetTargetMachineBuilder( TargetMachineBuilder targetMachineBuilder )
public void SetObjectLinkingLayerCreator( ObjectLayerFactory creator )
{
ArgumentNullException.ThrowIfNull( creator );
- ObjectLinkingLayerContextHandle = new( creator );
-
unsafe
{
+ ObjectLinkingLayerContext = creator.AsNativeContext();
+
LLVMOrcLLJITBuilderSetObjectLinkingLayerCreator(
Handle,
&NativeObjectLinkingLayerCreatorCallback,
- (void*)ObjectLinkingLayerContextHandle.AddRefAndGetNativeContext()
+ ObjectLinkingLayerContext
);
}
}
@@ -124,27 +137,14 @@ public static LLJitBuilder CreateBuilderForHost(
private void InvalidateAfterMove( )
{
Handle = default;
-
- // If the callback context handle exists and is allocated then free it now.
- // The handle for this builder itself is already closed so LLVM should not
- // have any callbacks for this to handle... (docs are silent on the point)
- if(!ObjectLinkingLayerContextHandle.IsInvalid && !ObjectLinkingLayerContextHandle.IsClosed)
- {
- ObjectLinkingLayerContextHandle.Dispose();
- }
-
- // Break any GC references to allow release
- InternalObjectLayerFactory = null;
}
private LLJitBuilder( LLVMOrcLLJITBuilderRef h )
{
Handle = h;
- ObjectLinkingLayerContextHandle = new( this );
}
- private SafeGCHandle ObjectLinkingLayerContextHandle;
- private ObjectLayerFactory? InternalObjectLayerFactory;
+ private unsafe void* ObjectLinkingLayerContext = null;
[UnmanagedCallersOnly( CallConvs = [ typeof( CallConvCdecl ) ] )]
[SuppressMessage( "Design", "CA1031:Do not catch general exception types", Justification = "REQUIRED for unmanaged callback - Managed exceptions must never cross the boundary to native code" )]
@@ -156,16 +156,17 @@ private LLJitBuilder( LLVMOrcLLJITBuilderRef h )
{
try
{
- if(MarshalGCHandle.TryGet( context, out LLJitBuilder? self ) && self.InternalObjectLayerFactory is not null)
+ if(NativeContext.TryFrom(context, out var self ))
{
using var managedTriple = new Triple(LazyEncodedString.FromUnmanaged(triple));
// caller takes ownership of the resulting handle; Don't Dispose it
- var factory = self.InternalObjectLayerFactory( new ExecutionSession( sessionRef ), managedTriple );
+ // It is returned via the raw native handle.
+ var factory = self( new ExecutionSession( sessionRef ), managedTriple );
return factory.Handle;
}
- // TODO: How/Can this report an error? Internally the result is (via a C++ lambda) that returns an "llvm::expected"
+ // TODO: How/Can this report an error? Internally the result is (via a C++ lambda) assigned to an "llvm::expected"
// but it is unclear if this method can return an error or what happens on a null return...
return 0; // This will probably crash in LLVM anyway - best effort.
}
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/MaterializerCallbacksHolder.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/MaterializerCallbacksHolder.cs
new file mode 100644
index 000000000..d8fe21647
--- /dev/null
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/MaterializerCallbacksHolder.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+namespace Ubiquity.NET.Llvm.OrcJITv2
+{
+ /// Holds delegates for performing custom materialization for a single materialization unit
+ ///
+ /// Instances of this type serve as the native context for callbacks. This is an internal holder of delegates
+ /// instead of an interface to allow for inline functions and lambdas etc.. for each action. This is not
+ /// possible if only an interface is used.
+ ///
+ internal sealed class MaterializerCallbacksHolder
+ : IMaterializerCallbacks
+ {
+ /// Initializes a new instance of the class.
+ /// Action to perform to materialize the symbol
+ /// Action to perform when the JIT discards/replaces a symbol
+ public MaterializerCallbacksHolder( MaterializationAction materializeAction, DiscardAction? discardAction )
+ {
+ MaterializeHandler = materializeAction;
+ DiscardHandler = discardAction;
+ }
+
+ public void Destroy( )
+ {
+ }
+
+ public void Discard( ref readonly JITDyLib jitLib, SymbolStringPoolEntry symbol )
+ {
+ if(DiscardHandler is not null)
+ {
+ DiscardHandler(in jitLib, symbol);
+ }
+ }
+
+ public void Materialize( MaterializationResponsibility r )
+ {
+ MaterializeHandler(r);
+ }
+
+ internal MaterializationAction MaterializeHandler { get; private set; }
+
+ internal DiscardAction? DiscardHandler { get; private set; }
+ }
+}
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/MemoryAllocatorNativeCallbacks.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/MemoryAllocatorNativeCallbacks.cs
new file mode 100644
index 000000000..7daa85323
--- /dev/null
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/MemoryAllocatorNativeCallbacks.cs
@@ -0,0 +1,105 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+namespace Ubiquity.NET.Llvm.OrcJITv2
+{
+ // Native only callbacks that use a GCHandle converted to a void* as the
+ // "context" to allow the native API to re-direct to the proper managed
+ // implementation instance. (That is, this is a reverse P/Invoke that
+ // handles marshalling of parameters and return type for native callers
+ // into managed code)
+
+ internal static class MemoryAllocatorNativeCallbacks
+ {
+ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
+ internal static unsafe void *CreatePerObjContextAsGlobalContext(void* outerContext)
+ {
+ // Provide the "global"/"outer" context as the "inner"/"Per OBJ" context
+ return outerContext;
+ }
+
+ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
+ internal static unsafe void DestroyPerObjContextNOP(void *_)
+ {
+ /* Intentional NOP */
+ }
+
+ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
+ internal static unsafe byte* AllocateCodeSection(void* ctx, nuint size, UInt32 alignment, UInt32 sectionId, byte* sectionName)
+ {
+#pragma warning disable CA2000 // Dispose objects before losing scope
+ // NOT dispsable, just "borrowed" via ctx
+ return NativeContext.TryFrom( ctx, out var self )
+ ? (byte*)self!.AllocateCodeSection(size, alignment, sectionId, LazyEncodedString.FromUnmanaged(sectionName))
+ : (byte*)null;
+#pragma warning restore CA2000 // Dispose objects before losing scope
+ }
+
+ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
+ internal static unsafe byte* AllocateDataSection(
+ void* ctx,
+ nuint size,
+ UInt32 alignment,
+ UInt32 sectionId,
+ byte* sectionName,
+ /*LLVMBool*/Int32 isReadOnly
+ )
+ {
+#pragma warning disable CA2000 // Dispose objects before losing scope
+ // NOT dispsable, just "borrowed" via ctx
+ return NativeContext.TryFrom( ctx, out var self )
+ ? (byte*)self!.AllocateDataSection(size, alignment, sectionId, LazyEncodedString.FromUnmanaged(sectionName), isReadOnly != 0)
+ : (byte*)null;
+#pragma warning restore CA2000 // Dispose objects before losing scope
+
+ }
+
+ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
+ internal static unsafe /*LLVMStatus*/ Int32 FinalizeMemory(void* ctx, byte** errMsg)
+ {
+ *errMsg = null;
+#pragma warning disable CA2000 // Dispose objects before losing scope
+ // NOT dispsable, just "borrowed" via ctx
+ if(NativeContext.TryFrom( ctx, out var self ))
+ {
+ if(!self!.FinalizeMemory(out LazyEncodedString? managedErrMsg))
+ {
+ AllocateAndSetNativeMessage( errMsg, managedErrMsg.ToReadOnlySpan(includeTerminator: true));
+ }
+ }
+#pragma warning restore CA2000 // Dispose objects before losing scope
+
+ AllocateAndSetNativeMessage( errMsg, "Invalid context provided to FinalizeMemory callback!"u8);
+ return 0;
+ }
+
+ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
+ internal static unsafe void NotifyTerminating(void* ctx)
+ {
+#pragma warning disable CA2000 // Dispose objects before losing scope
+ // NOT dispsable, just "borrowed" via ctx
+ if(NativeContext.TryFrom( ctx, out var self ))
+ {
+ // Don't dispose it here; But do release the context
+ // as no more callbacks will occur after this.
+ // Dispose controls the allocator handle itself, NOT the
+ // context used for the callbacks. This ONLY releases the
+ // callback context.
+ self.ReleaseContext();
+ }
+#pragma warning restore CA2000 // Dispose objects before losing scope
+ }
+
+ // WARNING: Native caller ***WILL*** call `free(*errMsg)` if `*errMsg != nullptr`!! [Undocumented!]
+ // Therefore, any error message returned should be allocated with NativeMemory.Alloc()
+ // to allow free() on the pointer.
+ private static unsafe void AllocateAndSetNativeMessage( byte** errMsg, ReadOnlySpan managedErrMsg )
+ {
+ nuint len = (nuint)managedErrMsg.Length;
+ void* p = NativeMemory.Alloc(len);
+ var nativeMsgSpan = new Span(p, (int)len);
+ managedErrMsg.CopyTo( nativeMsgSpan );
+ *errMsg = (byte*)p;
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/ObjectLayer.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/ObjectLayer.cs
index 04ab2a25b..60a0ff630 100644
--- a/src/Ubiquity.NET.Llvm/OrcJITv2/ObjectLayer.cs
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/ObjectLayer.cs
@@ -6,8 +6,14 @@
namespace Ubiquity.NET.Llvm.OrcJITv2
{
/// ORC JIT v2 Object linking layer
- public sealed class ObjectLayer
- : IDisposable
+ ///
+ /// Since instances of an Object Linking layer are ONLY ever created by and
+ /// returned directly to the native code as the raw handle, they are not generally disposable. They do
+ /// implement IDisposable as a means to enusre proper release in the face of an exception in an implementation
+ /// of but are not something that generally needs disposal at a managed level.
+ ///
+ public class ObjectLayer
+ : DisposableObject
{
/// Adds an object file to the specified library
/// Library to add the object file to
@@ -59,9 +65,10 @@ public void Emit( MaterializationResponsibility resp, MemoryBuffer objBuffer )
}
///
- [SuppressMessage("IDisposableAnalyzers.Correctness", "IDISP007:Don't dispose injected", Justification = "Ownership transferred in constructor")]
- public void Dispose( )
+ [SuppressMessage( "IDisposableAnalyzers.Correctness", "IDISP007:Don't dispose injected", Justification = "Ownership transferred in constructor" )]
+ protected override void Dispose( bool disposing )
{
+ base.Dispose( disposing );
if(!Handle.IsNull)
{
Handle.Dispose();
@@ -74,6 +81,25 @@ internal ObjectLayer( LLVMOrcObjectLayerRef h )
Handle = h;
}
- internal LLVMOrcObjectLayerRef Handle { get; private set; }
+ internal ObjectLayer()
+ {
+ }
+
+ internal LLVMOrcObjectLayerRef Handle
+ {
+ get;
+
+ // Only accessible from derived types, since the modifier of this property is `internal` that
+ // means `internal` AND `protected`
+ private protected set
+ {
+ if(!field.IsNull)
+ {
+ throw new InvalidOperationException("INTERNAL: Setting handle multiple times is not allowed!");
+ }
+
+ field = value;
+ }
+ }
}
}
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/SimpleJitMemoryAllocatorBase.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/SimpleJitMemoryAllocatorBase.cs
new file mode 100644
index 000000000..25b834b89
--- /dev/null
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/SimpleJitMemoryAllocatorBase.cs
@@ -0,0 +1,93 @@
+// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
+
+#pragma warning disable SA1649 // Filename must match type name
+#pragma warning disable SA1600 // Elements must be documented
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 'Type_or_Member'
+
+using static Ubiquity.NET.Llvm.Interop.ABI.llvm_c.ExecutionEngine;
+
+namespace Ubiquity.NET.Llvm.OrcJITv2
+{
+ /// Base class for a simple MCJIT style memory allocator
+ ///
+ /// Derived types need not be concrend with any of the low level native interop. Instead
+ /// they simply implement the abstract methods to perform the required allocations. This
+ /// base type handles all of the interop, including the reverse P/Invoke marhsalling for
+ /// callbacks.
+ ///
+ [Experimental("LLVMEXP005")]
+ public abstract class SimpleJitMemoryAllocatorBase
+ : DisposableObject
+ , IJitMemoryAllocator
+ {
+ protected SimpleJitMemoryAllocatorBase()
+ {
+ unsafe
+ {
+ CallbackContext = this.AsNativeContext();
+
+ Handle = LLVMCreateSimpleMCJITMemoryManager(
+ CallbackContext,
+ &MemoryAllocatorNativeCallbacks.AllocateCodeSection,
+ &MemoryAllocatorNativeCallbacks.AllocateDataSection,
+ &MemoryAllocatorNativeCallbacks.FinalizeMemory,
+ &MemoryAllocatorNativeCallbacks.NotifyTerminating
+ );
+ }
+ }
+
+ ///
+ public abstract nuint AllocateCodeSection(nuint size, UInt32 alignment, UInt32 sectionId, LazyEncodedString sectionName );
+
+ ///
+ public abstract nuint AllocateDataSection(nuint size, UInt32 alignment, UInt32 sectionId, LazyEncodedString sectionName, bool isReadOnly);
+
+ ///
+ public abstract bool FinalizeMemory([NotNullWhen(false)] out LazyEncodedString? errMsg);
+
+ ///
+ public virtual void ReleaseContext()
+ {
+ unsafe
+ {
+ NativeContext.Release(ref CallbackContext);
+ }
+ }
+
+ ///
+ protected override void Dispose( bool disposing )
+ {
+ base.Dispose( disposing );
+ if(disposing)
+ {
+ if(!Handle.IsNull)
+ {
+ Handle.Dispose();
+ Handle = default;
+ }
+
+ // Releases the allocated handle for the native code
+ // might not actually destroy anything. This is INTENTIONALLY done
+ // AFTER disposing the handle so that any pending call backs
+ // use a valid context. After the memory manager handle is destroyed
+ // no more call backs should occur so it is safe to release the
+ // context. (Ideally, the ReleaseContext() callback already happened
+ // but the LLVM docs, and code comments, are silent on the point. Thus,
+ // that uses a ref parameter that is set to null and a null check is
+ // applied here.)
+ unsafe
+ {
+ if(CallbackContext is not null)
+ {
+ NativeContext.Release(ref CallbackContext);
+ }
+ }
+ }
+ }
+
+ private unsafe void* CallbackContext;
+
+ private LLVMMCJITMemoryManagerRef Handle;
+ }
+}
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/SymbolStringPool.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/SymbolStringPool.cs
index 35f3da0aa..a1eb0f9df 100644
--- a/src/Ubiquity.NET.Llvm/OrcJITv2/SymbolStringPool.cs
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/SymbolStringPool.cs
@@ -1,8 +1,6 @@
// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information.
-using System.Collections.Immutable;
-
using static Ubiquity.NET.Llvm.Interop.ABI.libllvm_c.OrcJITv2Bindings;
using static Ubiquity.NET.Llvm.Interop.ABI.llvm_c.Orc;
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/SymbolStringPoolEntry.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/SymbolStringPoolEntry.cs
index b739422c4..0a066bc13 100644
--- a/src/Ubiquity.NET.Llvm/OrcJITv2/SymbolStringPoolEntry.cs
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/SymbolStringPoolEntry.cs
@@ -207,7 +207,7 @@ internal SymbolStringPoolEntry( LLVMOrcSymbolStringPoolEntryRef h, bool addRef =
/// Initializes a new instance of the class.
/// Native ABI handle
/// Indicates whether this is an aliased (unowned) representation
- /// Wraps a native ABI handle in a managed projected type
+ /// Wraps a native ABI handle for an entry in a string pool in a managed type
internal SymbolStringPoolEntry( nint abiHandle, bool alias = false )
{
Handle = LLVMOrcSymbolStringPoolEntryRef.FromABI(abiHandle);
diff --git a/src/Ubiquity.NET.Llvm/OrcJITv2/ThreadSafeModule.cs b/src/Ubiquity.NET.Llvm/OrcJITv2/ThreadSafeModule.cs
index b035bf5d2..9e4c78964 100644
--- a/src/Ubiquity.NET.Llvm/OrcJITv2/ThreadSafeModule.cs
+++ b/src/Ubiquity.NET.Llvm/OrcJITv2/ThreadSafeModule.cs
@@ -119,14 +119,14 @@ private static LLVMOrcThreadSafeModuleRef MakeHandle( ThreadSafeContext context,
{
try
{
- if(!MarshalGCHandle.TryGet( context, out GenericModuleOperation? self ))
+ if(!NativeContext.TryFrom(context, out var self ))
{
return LLVMErrorRef.CreateForNativeOut( "Internal Error: Invalid context provided for native callback"u8 );
}
IModule mod = new ModuleAlias(moduleHandle);
return mod is not null
- ? self( mod ).MoveToNative()
+ ? self!( mod ).MoveToNative()
: LLVMErrorRef.CreateForNativeOut( "Internal Error: Could not create wrapped module for native method"u8 );
}
catch(Exception ex)
diff --git a/src/Ubiquity.NET.Llvm/Types/IPointerType.cs b/src/Ubiquity.NET.Llvm/Types/IPointerType.cs
index aefceb968..49e167639 100644
--- a/src/Ubiquity.NET.Llvm/Types/IPointerType.cs
+++ b/src/Ubiquity.NET.Llvm/Types/IPointerType.cs
@@ -24,6 +24,16 @@ public interface IPointerType
ITypeRef? ElementType { get; init; }
}
+ // This does NOT use the new C# 14 extension syntax due to several reasons
+ // 1) Code lens does not work https://github.com/dotnet/roslyn/issues/79006 [Sadly marked as "not planned" - e.g., dead-end]
+ // 2) MANY analyzers get things wrong and need to be supressed (CA1000, CA1034, and many others [SAxxxx])
+ // 3) Many tools (like docfx don't support the new syntax yet)
+ // 4) No clear support for Caller* attributes ([CallerArgumentExpression(...)]).
+ //
+ // Bottom line it's a good idea with an incomplete implementation lacking support
+ // in the overall ecosystem. Don't use it unless you absolutely have to until all
+ // of that is sorted out.
+
/// Utility class to provide extensions for
///
/// These are useful even in the presence of default property implementations as the properties
@@ -43,12 +53,6 @@ public static class PointerTypeExtensions
/// but is not normal for anything that creates or clones the IR. Since the pointer type creation is
/// done as a method of the thing being pointed to this information is "attached" to the pointer so
/// that the is not .
- ///
- /// until C# 14 [.Net 10] is supported this is an extension method. Once C#14 is available
- /// then this can become a property. Default methods on the interface have too many restrictions
- /// (most egregious is the need to "box"/cast to the explicit interface for the lookup to find
- /// the method).
- ///
///
public static bool IsOpaque( this IPointerType ptr )
{
diff --git a/src/Ubiquity.NET.TextUX/AssemblyExtensions.cs b/src/Ubiquity.NET.TextUX/AssemblyExtensions.cs
index 384e2540d..15191612e 100644
--- a/src/Ubiquity.NET.TextUX/AssemblyExtensions.cs
+++ b/src/Ubiquity.NET.TextUX/AssemblyExtensions.cs
@@ -6,33 +6,20 @@
namespace Ubiquity.NET.TextUX
{
+ // This does NOT use the new C# 14 extension syntax due to several reasons
+ // 1) Code lens does not work https://github.com/dotnet/roslyn/issues/79006 [Sadly marked as "not planned" - e.g., dead-end]
+ // 2) MANY analyzers get things wrong and need to be supressed (CA1000, CA1034, and many others [SAxxxx])
+ // 3) Many tools (like docfx don't support the new syntax yet)
+ // 4) No clear support for Caller* attributes ([CallerArgumentExpression(...)]).
+ //
+ // Bottom line it's a good idea with an incomplete implementation lacking support
+ // in the overall ecosystem. Don't use it unless you absolutely have to until all
+ // of that is sorted out.
+
/// Utility class to provide extensions for consumers
[SuppressMessage( "Design", "CA1034:Nested types should not be visible", Justification = "BS, extension" )]
public static class AssemblyExtensions
{
-// Sadly support for the 'extension' keyword outside of the compiler is
-// spotty at best. Third party tools and analyzers don't know what to do
-// with it. First party analyzers and tools don't yet handle it properly.
-// (Looking at you VS 2026 Insider's preview!) So don't use it yet...
-#if ALL_TOOLS_SUPPORT_EXTENSION_KEYWORD
- /// Extensions for
- extension(Assembly asm)
- {
- /// Gets the value of the from an assembly
- [SuppressMessage( "Performance", "CA1822:Mark members as static", Justification = "BS, extension" )]
- public string InformationalVersion
- {
- get
- {
- var assemblyVersionAttribute = asm.GetCustomAttribute();
-
- return assemblyVersionAttribute is not null
- ? assemblyVersionAttribute.InformationalVersion
- : asm.GetName().Version?.ToString() ?? string.Empty;
- }
- }
- }
-#else
/// Gets the value of the from an assembly
/// Assembly to get informational version from
/// Information version of the assembly or an empty string if not available
@@ -45,6 +32,5 @@ public static string GetInformationalVersion(this Assembly self)
? assemblyVersionAttribute.InformationalVersion
: self.GetName().Version?.ToString() ?? string.Empty;
}
-#endif
}
}
diff --git a/src/Ubiquity.NET.TextUX/PackageReadMe.md b/src/Ubiquity.NET.TextUX/PackageReadMe.md
index e4e5d768d..4abdde830 100644
--- a/src/Ubiquity.NET.TextUX/PackageReadMe.md
+++ b/src/Ubiquity.NET.TextUX/PackageReadMe.md
@@ -6,7 +6,9 @@ Text based UI/UX. This is generally only relevant for console based apps.
`IDiagnosticReporter` interface is at the core of the UX. It is similar in many ways to many
of the logging interfaces available. The primary distinction is with the ***intention*** of
use. `IDiagnosticReporter` specifically assumes the use for UI/UX rather than a
-debugging/diagnostic log.
+debugging/diagnostic log. These have VERY distinct use cases and purposes and generally show
+very different information. (Not to mention the overlly complex requirements of
+the anti-pattern DI container assumed in `Microsoft.Extensions.Logging`)
### Messages
All messages for the UX use a simple immutable structure to store the details of a message
@@ -17,7 +19,8 @@ There are a few pre-built implementation of the `IDiagnosticReporter` interface.
* `TextWriterReporter`
* Base class for writing UX to a `TextWriter`
* `ConsoleReporter`
- * Reporter that reports errors to `Console.Error` and all other errors to `Console.Out`
+ * Reporter that reports errors to `Console.Error` and all other nessages to
+ `Console.Out`
* `ColoredConsoleReporter`
* `ConsoleReporter` that colorizes output using ANSI color codes
* Colors are customizable, but contains a common default
diff --git a/src/Ubiquity.NET.TextUX/SourceRange.cs b/src/Ubiquity.NET.TextUX/SourceRange.cs
index e2ac00fd7..19e842147 100644
--- a/src/Ubiquity.NET.TextUX/SourceRange.cs
+++ b/src/Ubiquity.NET.TextUX/SourceRange.cs
@@ -11,6 +11,11 @@ namespace Ubiquity.NET.TextUX
// Column position of the location [0..n-1]
/// Abstraction to hold a source location range as a pair of values
+ ///
+ /// It is possible that some sources do not provide dual points to make a proper range. This supports such
+ /// "ranges" as a single point that is not sliceable. This allows callers to deal with sources that only
+ /// contain the start point (Looking at you !).
+ ///
public readonly record struct SourceRange
: IFormattable
{