Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
<PackageReference Update="OpenSoftware.DgmlBuilder" Version="1.14.0" />
<PackageReference Update="System.Collections.Immutable" Version="1.7.0" />
<!-- See: https://github.com/dotnet/docfx/issues/5536 before updating...-->
<PackageReference Update="docfx.console" Version="2.48.1" />
<PackageReference Update="memberpage" Version="2.49.0" />
<PackageReference Update="docfx.console" Version="2.56.6" />
<PackageReference Update="memberpage" Version="2.56.6" />
<PackageReference Update="msdn.4.5.2" Version="0.1.0-alpha-1611021200" />
<PackageReference Update="YamlDotNet" Version="8.1.0" />
<PackageReference Update="google-diff-match-patch" Version="1.1.24" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,26 @@ public override IEnumerable<CppSharp.AST.Attribute> Attributes

public override QualifiedType TransformType( QualifiedType type )
{
return new QualifiedType( new ArrayType( ) { QualifiedType = ( type.Type as PointerType ).QualifiedPointee } );
// attempt to get a more precise type than sbyte* for byte sized values and bool
QualifiedType elementType;
switch(SubType)
{
case UnmanagedType.Bool:
elementType = new QualifiedType( new BuiltinType( PrimitiveType.Bool ), type.Qualifiers);
break;

case UnmanagedType.I1:
elementType = new QualifiedType( new BuiltinType( PrimitiveType.SChar ), type.Qualifiers );
break;
case UnmanagedType.U1:
elementType = new QualifiedType( new BuiltinType( PrimitiveType.UChar ), type.Qualifiers );
break;
default:
elementType = ( type.Type as PointerType ).QualifiedPointee;
break;
}

return new QualifiedType( new ArrayType( ) { QualifiedType = elementType } );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
This means that this project can't use language features > 7.3 (Limits for .NET Framework)
-->
<TargetFramework>net47</TargetFramework>
<Nullable/>
<Nullable />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" />
<PackageReference Include="Ubiquity.ArgValidators" />
<PackageReference Include="CppSharp" />
<PackageReference Include="YamlDotNet" />
Expand Down
39 changes: 39 additions & 0 deletions src/Interop/LlvmBindingsGenerator/Options.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// -----------------------------------------------------------------------
// <copyright file="Options.cs" company="Ubiquity.NET Contributors">
// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Diagnostics.CodeAnalysis;

using CommandLine;

using CppSharp;

namespace LlvmBindingsGenerator
{
[SuppressMessage("Build", "CA1812", Justification = "Instantiated via reflection from commandline parser" )]
internal class Options
{
public Options( string llvmRoot, string extensionsRoot, string outputPath, DiagnosticKind diagnostics )
{
LlvmRoot = llvmRoot;
ExtensionsRoot = extensionsRoot;
OutputPath = outputPath;
Diagnostics = diagnostics;
}

[Value( 0, MetaName = "LLVM Root", HelpText = "Root of source with the LLVM headers to parse", Required = true )]
public string LlvmRoot { get; }

[Value( 1, MetaName = "Extensions Root", HelpText = "Root of source with the LibLLVM extension headers to parse", Required = true )]
public string ExtensionsRoot { get; }

[Value( 2, MetaName = "Output Path", HelpText = "Root of the output to place the generated code", Required = true )]
public string OutputPath { get; } = Environment.CurrentDirectory;

[Option( HelpText = "Diagnostics output level", Required = false, Default = DiagnosticKind.Message )]
public DiagnosticKind Diagnostics { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ select attrib
).Any( );
if( !hasCustomMarshaling )
{
Diagnostics.Error( "ERROR: Function {0} has string return type, but does not have string custom marshaling attribute to define marshaling behavior!", function.Name );
Diagnostics.Error( "ERROR: Function '{0}' has string return type, but does not have string custom marshaling attribute to define marshaling behavior!", function.Name );
}
}
else if( function.ReturnType.Type is PointerType pt )
Expand All @@ -69,7 +69,7 @@ select attrib

if( !allowUnsafeReturn )
{
Diagnostics.Error( "ERROR: Function {0} has unsafe return type '{1}', without a marshaling attribute - (Possible missing FunctionBindings entry)"
Diagnostics.Error( "ERROR: Function '{0}' has unsafe return type '{1}', without a marshaling attribute - (Possible missing FunctionBindings entry)"
, function.Name
, pt.ToString( )
);
Expand All @@ -85,7 +85,22 @@ select attrib

foreach( var param in outStrings )
{
Diagnostics.Error( "ERROR: Parameter {0} of function {1} is an out string but has no custom marshaling attribute to define marshaling behavior", param.Name, function.Name );
Diagnostics.Error( "ERROR: Parameter '{0}' of function '{1}' is an out string but has no custom marshaling attribute to define marshaling behavior", param.Name, function.Name );
}

// indicate input char* params without marshalling.
// Generally these are legitimately strings (and there are a LOT of them) so this is a debug diagnostic
// to use when updating the bindings generation for a new version of LLVM or detecting cases where the
// signature is wrong.
var inStrings = from p in function.Parameters
where p.IsIn
&& ( p.Type.ToString( ) == "string" || p.Type.ToString() == "sbyte")
&& !p.Attributes.Any( a=> IsStringMarshalingAttribute(a) || IsArrayMarshalingAttribute(a) )
select p;

foreach( var param in inStrings )
{
Diagnostics.Debug( "NOTE: Parameter '{0}' of function '{1}' is an in pointer but has no custom marshaling attribute to define marshaling behavior, string is assumed", param.Name, function.Name );
}

return true;
Expand All @@ -97,6 +112,12 @@ private static bool IsStringMarshalingAttribute( CppSharp.AST.Attribute attribut
&& attribute.Value.StartsWith( "UnmanagedType.CustomMarshaler", System.StringComparison.InvariantCulture );
}

private static bool IsArrayMarshalingAttribute( CppSharp.AST.Attribute attribute )
{
return attribute.Type.Name == "MarshalAsAttribute"
&& attribute.Value.StartsWith( "UnmanagedType.LPArray", System.StringComparison.InvariantCulture );
}

private readonly IGeneratorConfig Config;
}
}
24 changes: 12 additions & 12 deletions src/Interop/LlvmBindingsGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
using System.IO;
using System.Reflection;

using CppSharp;
using CommandLine;

using CppSharp;
using LlvmBindingsGenerator.Configuration;
using LlvmBindingsGenerator.Configuration.Yaml;

Expand All @@ -18,25 +19,24 @@ internal static class Program
{
public static int Main( string[ ] args )
{
var diagnostics = new ErrorTrackingDiagnostics( );
Diagnostics.Implementation = diagnostics;
return Parser.Default.ParseArguments<Options>( args ).MapResult( Run, _ => -1 );
}

if( args.Length < 2 )
private static int Run( Options options )
{
var diagnostics = new ErrorTrackingDiagnostics( )
{
Diagnostics.Error( "USAGE: LlvmBindingsGenerator <llvmRoot> <extensionsRoot> [OutputPath]" );
return -1;
}
Level = options.Diagnostics
};

string llvmRoot = args[ 0 ];
string extensionsRoot = args[ 1 ];
string outputPath = args.Length > 2 ? args[ 2 ] : System.Environment.CurrentDirectory;
Diagnostics.Implementation = diagnostics;

// read in the binding configuration from the YAML file
// It is hoped, that going forward, the YAML file is the only thing that needs to change
// but either way, helps keep the declarative part in a more easily understood format.
// but either way, it helps keep the declarative part in a more easily edited format.
string configPath = Path.Combine( Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location ), "BindingsConfig.yml");
var config = new ReadOnlyConfig( YamlConfiguration.ParseFrom( configPath ) );
var library = new LibLlvmGeneratorLibrary( config, llvmRoot, extensionsRoot, outputPath );
var library = new LibLlvmGeneratorLibrary( config, options.LlvmRoot, options.ExtensionsRoot, options.OutputPath );
Driver.Run( library );
return diagnostics.ErrorCount;
/* TODO:
Expand Down
14 changes: 13 additions & 1 deletion src/Interop/LlvmBindingsGenerator/bindingsConfig.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
#
# !Array
# SubType - the unmanaged type of the elements of the array (See: System.Runtime.InteropServices.UnmanagedType enum for details)
# SizeParam - optional integer index of an out parmater that contains the size of the array
# SizeParam - optional integer index of an out parmater that contains the size of the array [for out params]
#
FunctionBindings:
- Name: LLVMMDStringInContext
Expand Down Expand Up @@ -191,10 +191,12 @@ FunctionBindings:
- Name: LLVMStartMultithreaded
IsObsolete: True
DeprecationMessage: "This function is deprecated, multi-threading support is a compile-time variable and cannot be changed at run-time"
IsProjected: False

- Name: LLVMStopMultithreaded
IsObsolete: True
DeprecationMessage: "This function is deprecated, multi-threading support is a compile-time variable and cannot be changed at run-time"
IsProjected: False

- Name: LLVMCreateBinary
ParamTransforms:
Expand Down Expand Up @@ -1076,6 +1078,16 @@ FunctionBindings:
- Name: LLVMSymbolLookupCallback
ReturnTransform: !String {Kind: CopyAlias}

# lifetime management of shared ownership between managed and unmanaged semantics in .NET is dodgy at best
# so this is exported, but not projected to allow for experimentation until a safe way of managing
# the shared/pinned nature of things is determined
- Name: LLVMCreateMemoryBufferWithMemoryRange
IsProjected: false

- Name: LLVMCreateMemoryBufferWithMemoryRangeCopy
ParamTransforms:
- !Array { Name: InputData, Semantics: In }

# The HandleMap Lists the types of handles and their disposal semantics
#HandleMap:
# - !ContextHandle { HandleName: LLVMTypeRef}
Expand Down
38 changes: 38 additions & 0 deletions src/Ubiquity.NET.Llvm.Tests/MemoryBufferTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

using System;
using System.IO;
using System.Linq;
using System.Text;

using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down Expand Up @@ -84,5 +85,42 @@ public void Slice_Provides_a_Partial_View_of_the_Data( )
byte[ ] expected = Encoding.ASCII.GetBytes( TestData );
Assert.IsTrue( expected.AsSpan( 5, 2 ).SequenceEqual( result ) );
}

[TestMethod]
public void Buffer_from_array_with_null_name_succeeds( )
{
byte[ ] data = Range(0, 255);
var buffer = new MemoryBuffer(data, null);
Assert.IsNotNull( buffer );
Assert.AreEqual( 255, buffer.Size );
}

[TestMethod]
public void Buffer_from_array_with_valid_name_succeeds( )
{
byte[ ] data = Range(0, 255);
var buffer = new MemoryBuffer(data, "testName");
Assert.IsNotNull( buffer );
Assert.AreEqual( 255, buffer.Size );
}

[TestMethod]
public void Buffer_from_array_contains_correct_data( )
{
byte[ ] data = Range(0, 255);
var buffer = new MemoryBuffer(data);
Assert.IsNotNull( buffer );
Assert.AreEqual( 255, buffer.Size );
var span = buffer.Slice();
for(int i = 0; i < data.Length; ++i )
{
Assert.AreEqual( data[ i ], span[ i ], $"Index {i}" );
}
}

private static byte[] Range(byte start, byte length)
{
return Enumerable.Range( start, length ).Select( n => ( byte )n ).ToArray( );
}
}
}
20 changes: 6 additions & 14 deletions src/Ubiquity.NET.Llvm/BitcodeModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -924,12 +924,9 @@ public static BitcodeModule LoadFrom( MemoryBuffer buffer, Context context )
buffer.ValidateNotNull( nameof( buffer ) );
context.ValidateNotNull( nameof( context ) );

if( LLVMParseBitcodeInContext2( context.ContextHandle, buffer.BufferHandle, out LLVMModuleRef modRef ).Failed )
{
throw new InternalCodeGeneratorException( Resources.Could_not_parse_bit_code_from_buffer );
}

return context.GetModuleFor( modRef );
return LLVMParseBitcodeInContext2( context.ContextHandle, buffer.BufferHandle, out LLVMModuleRef modRef ).Failed
? throw new InternalCodeGeneratorException( Resources.Could_not_parse_bit_code_from_buffer )
: context.GetModuleFor( modRef );
}

internal LLVMModuleRef? ModuleHandle { get; private set; }
Expand All @@ -943,7 +940,6 @@ internal LLVMModuleRef Detach( )
return retVal!; // can't be null as ThrowIfDisposed would consider it disposed
}

[SuppressMessage( "Reliability", "CA2000:Dispose objects before losing scope", Justification = "Context created here is owned, and disposed of via the ContextCache" )]
internal static BitcodeModule? FromHandle( LLVMModuleRef nativeHandle )
{
nativeHandle.ValidateNotDefault( nameof( nativeHandle ) );
Expand Down Expand Up @@ -996,16 +992,12 @@ internal InterningFactory( Context context )
private protected override BitcodeModule ItemFactory( LLVMModuleRef handle )
{
var contextRef = LLVMGetModuleContext( handle );
if( Context.ContextHandle != contextRef )
{
throw new ArgumentException( Resources.Context_mismatch_cannot_cache_modules_from_multiple_contexts );
}

return new BitcodeModule( handle );
return Context.ContextHandle != contextRef
? throw new ArgumentException( Resources.Context_mismatch_cannot_cache_modules_from_multiple_contexts )
: new BitcodeModule( handle );
}
}

[SuppressMessage( "Reliability", "CA2000:Dispose objects before losing scope", Justification = "Context created here is owned, and disposed of via the ContextCache" )]
private BitcodeModule( LLVMModuleRef handle )
{
handle.ValidateNotDefault( nameof( handle ) );
Expand Down
11 changes: 8 additions & 3 deletions src/Ubiquity.NET.Llvm/JIT/OrcJit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;

Expand All @@ -22,6 +23,7 @@ namespace Ubiquity.NET.Llvm.JIT
/// The LLVM OrcJIT supports lazy compilation and better resource management for
/// clients. For more details on the implementation see the LLVM Documentation.
/// </remarks>
[SuppressMessage( "Style", "IDE0046:Convert to conditional expression", Justification = "multiple levels of the ternary conditional expression is anything but a simplification" )]
public class OrcJit
: DisposableObject
, ILazyCompileExecutionEngine
Expand Down Expand Up @@ -107,9 +109,12 @@ public ulong DefaultSymbolResolver( string name, IntPtr ctx )
throw new InvalidOperationException( string.Format( CultureInfo.CurrentCulture, Resources.Unresolved_Symbol_0_1, name, LLVMOrcGetErrorMsg( JitStackHandle ) ) );
}

return retAddr != 0
? retAddr
: GlobalInteropFunctions.TryGetValue( name, out WrappedNativeCallback callBack ) ? ( ulong )callBack.ToIntPtr( ).ToInt64( ) : 0;
if( retAddr != 0)
{
return retAddr;
}

return GlobalInteropFunctions.TryGetValue( name, out WrappedNativeCallback callBack ) ? ( ulong )callBack.ToIntPtr( ).ToInt64( ) : 0;
}
#pragma warning disable CA1031 // Do not catch general exception types
catch
Expand Down
14 changes: 14 additions & 0 deletions src/Ubiquity.NET.Llvm/MemoryBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ public MemoryBuffer( string path )
BufferHandle = handle;
}

/// <summary>Initializes a new instance of the <see cref="MemoryBuffer"/> class from a byte array</summary>
/// <param name="data">Array of bytes to copy into the memory buffer</param>
/// <param name="name">Name of the buffer (for diagnostics)</param>
/// <remarks>
/// This constructor makes a copy of the data array as a <see cref="MemoryBuffer"/> the memory in the buffer
/// is unmanaged memory usable by the LLVM native code. It is released in the Dispose method
/// </remarks>
public MemoryBuffer( byte[] data, string? name = null)
{
data.ValidateNotNull( nameof( data ) );
BufferHandle = LLVMCreateMemoryBufferWithMemoryRangeCopy( data, data.Length, name ?? string.Empty )
.ThrowIfInvalid( );
}

/// <summary>Gets the size of the buffer</summary>
public int Size => BufferHandle.IsInvalid ? 0 : ( int )LLVMGetBufferSize( BufferHandle );

Expand Down
2 changes: 1 addition & 1 deletion stylecop.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"namingRules":
{
"allowCommonHungarianPrefixes": false,
"allowedHungarianPrefixes": ["h", "di", "op", "os", "ir", "is", "do", "un", "on", "to", "if", "no", "v"]
"allowedHungarianPrefixes": ["h", "di", "op", "os", "ir", "is", "do", "un", "on", "to", "if", "no", "v","in"]
},
"orderingRules":
{
Expand Down