From 7ed7cedae6f73ac74b7e881819e3a449616e23ef Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 15 Jul 2026 17:30:18 -0700 Subject: [PATCH 1/4] Add a managed Module type mirroring the C++ llvm::Module Introduces a context-scoped ownership registry on LLVMContext so a Module is a cached, non-owning wrapper (matching the other managed value/type wrappers) and fills out the full accessor surface: identity, source file name, target triple, data-layout string, and module inline asm; function and global lookup, insertion, and enumeration; named struct-type lookup; and print/verify/clone/bitcode. Also exposes ModuleIdentifier, SourceFileName, and InlineAsm on LLVMModuleRef. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Extensions/LLVMModuleRef.cs | 81 +++++++ sources/LLVMSharp/LLVMContext.cs | 23 ++ sources/LLVMSharp/Module.cs | 221 ++++++++++++++++++ tests/LLVMSharp.UnitTests/ManagedApi.cs | 42 ++++ 4 files changed, 367 insertions(+) diff --git a/sources/LLVMSharp.Interop/Extensions/LLVMModuleRef.cs b/sources/LLVMSharp.Interop/Extensions/LLVMModuleRef.cs index a820e00c..8245fdba 100644 --- a/sources/LLVMSharp.Interop/Extensions/LLVMModuleRef.cs +++ b/sources/LLVMSharp.Interop/Extensions/LLVMModuleRef.cs @@ -73,6 +73,33 @@ public readonly int IsNewDbgInfoFormat } } + public readonly string InlineAsm + { + get + { + if (Handle == IntPtr.Zero) + { + return string.Empty; + } + + nuint len; + var pInlineAsm = LLVM.GetModuleInlineAsm(this, &len); + + if (pInlineAsm == null) + { + return string.Empty; + } + + return SpanExtensions.AsString(pInlineAsm); + } + + set + { + using var marshaledInlineAsm = new MarshaledString(value.AsSpan()); + LLVM.SetModuleInlineAsm2(this, marshaledInlineAsm, (nuint)marshaledInlineAsm.Length); + } + } + public readonly LLVMValueRef LastFunction => (Handle != IntPtr.Zero) ? LLVM.GetLastFunction(this) : default; public readonly LLVMValueRef LastGlobal => (Handle != IntPtr.Zero) ? LLVM.GetLastGlobal(this) : default; @@ -85,6 +112,60 @@ public readonly int IsNewDbgInfoFormat public readonly LLVMModuleNamedMetadataEnumerable NamedMetadata => new LLVMModuleNamedMetadataEnumerable(this); + public readonly string ModuleIdentifier + { + get + { + if (Handle == IntPtr.Zero) + { + return string.Empty; + } + + nuint len; + var pModuleIdentifier = LLVM.GetModuleIdentifier(this, &len); + + if (pModuleIdentifier == null) + { + return string.Empty; + } + + return SpanExtensions.AsString(pModuleIdentifier); + } + + set + { + using var marshaledModuleIdentifier = new MarshaledString(value.AsSpan()); + LLVM.SetModuleIdentifier(this, marshaledModuleIdentifier, (nuint)marshaledModuleIdentifier.Length); + } + } + + public readonly string SourceFileName + { + get + { + if (Handle == IntPtr.Zero) + { + return string.Empty; + } + + nuint len; + var pSourceFileName = LLVM.GetSourceFileName(this, &len); + + if (pSourceFileName == null) + { + return string.Empty; + } + + return SpanExtensions.AsString(pSourceFileName); + } + + set + { + using var marshaledSourceFileName = new MarshaledString(value.AsSpan()); + LLVM.SetSourceFileName(this, marshaledSourceFileName, (nuint)marshaledSourceFileName.Length); + } + } + public readonly string Target { get diff --git a/sources/LLVMSharp/LLVMContext.cs b/sources/LLVMSharp/LLVMContext.cs index 613429a1..40847f03 100644 --- a/sources/LLVMSharp/LLVMContext.cs +++ b/sources/LLVMSharp/LLVMContext.cs @@ -17,6 +17,7 @@ public sealed class LLVMContext : IEquatable private readonly Dictionary> _createdValues = []; private readonly Dictionary> _createdTypes = []; + private readonly Dictionary> _createdModules = []; public LLVMContext() : this(LLVMContextRef.Create()) { @@ -127,4 +128,26 @@ internal TValue GetOrCreate(LLVMValueRef handle) internal Type GetOrCreate(LLVMTypeRef handle) => GetOrCreate(handle); internal Value GetOrCreate(LLVMValueRef handle) => GetOrCreate(handle); + + internal Module GetOrCreate(LLVMModuleRef handle) + { + WeakReference? moduleRef; + + if (handle == null) + { + return null!; + } + else if (!_createdModules.TryGetValue(handle, out moduleRef)) + { + moduleRef = new WeakReference(null!); + _createdModules.Add(handle, moduleRef); + } + + if (!moduleRef.TryGetTarget(out var module)) + { + module = new Module(handle); + moduleRef.SetTarget(module); + } + return module; + } } diff --git a/sources/LLVMSharp/Module.cs b/sources/LLVMSharp/Module.cs index c515d054..20b8377a 100644 --- a/sources/LLVMSharp/Module.cs +++ b/sources/LLVMSharp/Module.cs @@ -1,14 +1,235 @@ // Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. using System; +using System.Collections.Generic; using LLVMSharp.Interop; namespace LLVMSharp; public sealed class Module : IEquatable { + internal Module(LLVMModuleRef handle) + { + Handle = handle; + } + public LLVMModuleRef Handle { get; } + public LLVMContext Context => LLVMContext.GetOrCreate(Handle.Context); + + public string DataLayoutString + { + get + { + return Handle.DataLayout; + } + + set + { + var handle = Handle; + handle.DataLayout = value; + } + } + + public string InlineAsm + { + get + { + return Handle.InlineAsm; + } + + set + { + var handle = Handle; + handle.InlineAsm = value; + } + } + + public string ModuleIdentifier + { + get + { + return Handle.ModuleIdentifier; + } + + set + { + var handle = Handle; + handle.ModuleIdentifier = value; + } + } + + public string SourceFileName + { + get + { + return Handle.SourceFileName; + } + + set + { + var handle = Handle; + handle.SourceFileName = value; + } + } + + public string TargetTriple + { + get + { + return Handle.Target; + } + + set + { + var handle = Handle; + handle.Target = value; + } + } + + public static Module Create(LLVMContext context, string name) + { + ArgumentNullException.ThrowIfNull(context); + return context.GetOrCreate(context.Handle.CreateModuleWithName(name)); + } + + public Module Clone() => Context.GetOrCreate(Handle.Clone()); + + public Function AddFunction(string name, FunctionType functionType) + { + ArgumentNullException.ThrowIfNull(functionType); + return Context.GetOrCreate(Handle.AddFunction(name, functionType.Handle)); + } + + public GlobalAlias AddAlias(Type valueType, uint addressSpace, Constant aliasee, string name) + { + ArgumentNullException.ThrowIfNull(valueType); + ArgumentNullException.ThrowIfNull(aliasee); + return Context.GetOrCreate(Handle.AddAlias2(valueType.Handle, addressSpace, aliasee.Handle, name)); + } + + public GlobalVariable AddGlobal(Type type, string name) + { + ArgumentNullException.ThrowIfNull(type); + return Context.GetOrCreate(Handle.AddGlobal(type.Handle, name)); + } + + public GlobalVariable AddGlobalInAddressSpace(Type type, string name, uint addressSpace) + { + ArgumentNullException.ThrowIfNull(type); + return Context.GetOrCreate(Handle.AddGlobalInAddressSpace(type.Handle, name, addressSpace)); + } + + public void Dump() => Handle.Dump(); + + public Function? GetFunction(string name) + { + var handle = Handle.GetNamedFunction(name); + return (handle.Handle != IntPtr.Zero) ? Context.GetOrCreate(handle) : null; + } + + public Function[] GetFunctions() + { + var result = new List(); + var context = Context; + + foreach (var handle in Handle.Functions) + { + result.Add(context.GetOrCreate(handle)); + } + + return [.. result]; + } + + public GlobalAlias[] GetGlobalAliases() + { + var result = new List(); + var context = Context; + + foreach (var handle in Handle.GlobalAliases) + { + result.Add(context.GetOrCreate(handle)); + } + + return [.. result]; + } + + public GlobalIFunc[] GetGlobalIFuncs() + { + var result = new List(); + var context = Context; + + foreach (var handle in Handle.GlobalIFuncs) + { + result.Add(context.GetOrCreate(handle)); + } + + return [.. result]; + } + + public GlobalVariable? GetGlobalVariable(string name) + { + var handle = Handle.GetNamedGlobal(name); + return (handle.Handle != IntPtr.Zero) ? Context.GetOrCreate(handle) : null; + } + + public GlobalVariable[] GetGlobalVariables() + { + var result = new List(); + var context = Context; + + foreach (var handle in Handle.Globals) + { + result.Add(context.GetOrCreate(handle)); + } + + return [.. result]; + } + + public StructType[] GetIdentifiedStructTypes() + { + var handles = Handle.GetIdentifiedStructTypes(); + + if (handles.Length == 0) + { + return []; + } + + var context = Context; + var result = new StructType[handles.Length]; + + for (var i = 0; i < result.Length; i++) + { + result[i] = context.GetOrCreate(handles[i]); + } + + return result; + } + + public Function GetOrInsertFunction(string name, FunctionType functionType) + { + ArgumentNullException.ThrowIfNull(functionType); + return GetFunction(name) ?? AddFunction(name, functionType); + } + + public StructType? GetTypeByName(string name) + { + var handle = Handle.GetTypeByName(name); + return (handle.Handle != IntPtr.Zero) ? Context.GetOrCreate(handle) : null; + } + + public void PrintToFile(string filename) => Handle.PrintToFile(filename); + + public string PrintToString() => Handle.PrintToString(); + + public bool TryPrintToFile(string filename, out string errorMessage) => Handle.TryPrintToFile(filename, out errorMessage); + + public bool TryVerify(LLVMVerifierFailureAction action, out string message) => Handle.TryVerify(action, out message); + + public void Verify(LLVMVerifierFailureAction action) => Handle.Verify(action); + + public int WriteBitcodeToFile(string path) => Handle.WriteBitcodeToFile(path); + public static bool operator ==(Module? left, Module? right) => ReferenceEquals(left, right) || (left?.Handle == right?.Handle); public static bool operator !=(Module? left, Module? right) => !(left == right); diff --git a/tests/LLVMSharp.UnitTests/ManagedApi.cs b/tests/LLVMSharp.UnitTests/ManagedApi.cs index c73cd9ec..550c122b 100644 --- a/tests/LLVMSharp.UnitTests/ManagedApi.cs +++ b/tests/LLVMSharp.UnitTests/ManagedApi.cs @@ -403,4 +403,46 @@ public void GepAndAtomicAccessors() var fence = (FenceInst)context.GetOrCreate(builder.BuildFence(LLVMAtomicOrdering.LLVMAtomicOrderingAcquire, singleThread: false, "fence")); Assert.That(fence.Ordering, Is.EqualTo(AtomicOrdering.Acquire)); } + + [Test] + public void ModuleAccessors() + { + var context = new LLVMContext(); + var module = Module.Create(context, "m"); + var int32 = Type.GetInt32Ty(context); + + Assert.That(module.ModuleIdentifier, Is.EqualTo("m")); + Assert.That(module.Context, Is.EqualTo(context)); + + module.SourceFileName = "source.ll"; + module.TargetTriple = "x86_64-pc-windows-msvc"; + module.DataLayoutString = "e-m:w-p:64:64"; + module.InlineAsm = "nop"; + + Assert.That(module.SourceFileName, Is.EqualTo("source.ll")); + Assert.That(module.TargetTriple, Is.EqualTo("x86_64-pc-windows-msvc")); + Assert.That(module.DataLayoutString, Is.EqualTo("e-m:w-p:64:64")); + Assert.That(module.InlineAsm, Does.Contain("nop")); + + var functionType = LLVMTypeRef.CreateFunction(int32.Handle, [int32.Handle], IsVarArg: false); + var function = module.AddFunction("f", (FunctionType)context.GetOrCreate(functionType)); + var global = module.AddGlobal(int32, "g"); + + Assert.That(module.GetFunction("f"), Is.EqualTo(function)); + Assert.That(module.GetFunction("missing"), Is.Null); + Assert.That(module.GetGlobalVariable("g"), Is.EqualTo(global)); + Assert.That(module.GetOrInsertFunction("f", (FunctionType)context.GetOrCreate(functionType)), Is.EqualTo(function)); + Assert.That(module.GetFunctions().Length, Is.EqualTo(1)); + Assert.That(module.GetGlobalVariables().Length, Is.EqualTo(1)); + + var structType = StructType.Create(context, "S"); + structType.SetBody([int32], packed: false); + _ = module.AddGlobal(structType, "s"); + Assert.That(module.GetTypeByName("S"), Is.EqualTo(structType)); + + Assert.That(module.TryVerify(LLVMVerifierFailureAction.LLVMReturnStatusAction, out _), Is.True); + + var clone = module.Clone(); + Assert.That(clone.GetFunction("f"), Is.Not.Null); + } } From 3cd6525e2b7c338f79f2a48f7fbaf777fb7ba365 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 15 Jul 2026 17:30:30 -0700 Subject: [PATCH 2/4] Add navigation and iteration accessors to the managed BasicBlock Fills out BasicBlock to mirror llvm::BasicBlock: parent function, terminator, first/last instruction, next/previous block, instruction enumeration, and the move/remove/erase helpers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- sources/LLVMSharp/Values/BasicBlock.cs | 84 +++++++++++++++++++++++++ tests/LLVMSharp.UnitTests/ManagedApi.cs | 28 +++++++++ 2 files changed, 112 insertions(+) diff --git a/sources/LLVMSharp/Values/BasicBlock.cs b/sources/LLVMSharp/Values/BasicBlock.cs index f5db5bd8..1f066d43 100644 --- a/sources/LLVMSharp/Values/BasicBlock.cs +++ b/sources/LLVMSharp/Values/BasicBlock.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. using System; +using System.Collections.Generic; using LLVMSharp.Interop; namespace LLVMSharp; @@ -50,4 +51,87 @@ public static BasicBlock Create(LLVMContext context, ReadOnlySpan name, Ba public new LLVMBasicBlockRef Handle { get; } public LLVMValueRef ValueHandle => base.Handle; + + public Instruction? FirstInstruction + { + get + { + var handle = Handle.FirstInstruction; + return (handle.Handle != IntPtr.Zero) ? Context.GetOrCreate(handle) : null; + } + } + + public Instruction? LastInstruction + { + get + { + var handle = Handle.LastInstruction; + return (handle.Handle != IntPtr.Zero) ? Context.GetOrCreate(handle) : null; + } + } + + public BasicBlock? Next + { + get + { + var handle = Handle.Next; + return (handle.Handle != IntPtr.Zero) ? Context.GetOrCreate(handle) : null; + } + } + + public Function? Parent + { + get + { + var handle = Handle.Parent; + return (handle.Handle != IntPtr.Zero) ? Context.GetOrCreate(handle) : null; + } + } + + public BasicBlock? Previous + { + get + { + var handle = Handle.Previous; + return (handle.Handle != IntPtr.Zero) ? Context.GetOrCreate(handle) : null; + } + } + + public Instruction? Terminator + { + get + { + var handle = Handle.Terminator; + return (handle.Handle != IntPtr.Zero) ? Context.GetOrCreate(handle) : null; + } + } + + public void EraseFromParent() => Handle.Delete(); + + public Instruction[] GetInstructions() + { + var result = new List(); + var context = Context; + + foreach (var handle in Handle.Instructions) + { + result.Add(context.GetOrCreate(handle)); + } + + return [.. result]; + } + + public void MoveAfter(BasicBlock target) + { + ArgumentNullException.ThrowIfNull(target); + Handle.MoveAfter(target.Handle); + } + + public void MoveBefore(BasicBlock target) + { + ArgumentNullException.ThrowIfNull(target); + Handle.MoveBefore(target.Handle); + } + + public void RemoveFromParent() => Handle.RemoveFromParent(); } diff --git a/tests/LLVMSharp.UnitTests/ManagedApi.cs b/tests/LLVMSharp.UnitTests/ManagedApi.cs index 550c122b..4f6ed3ed 100644 --- a/tests/LLVMSharp.UnitTests/ManagedApi.cs +++ b/tests/LLVMSharp.UnitTests/ManagedApi.cs @@ -445,4 +445,32 @@ public void ModuleAccessors() var clone = module.Clone(); Assert.That(clone.GetFunction("f"), Is.Not.Null); } + + [Test] + public void BasicBlockAccessors() + { + var context = new LLVMContext(); + var module = Module.Create(context, "m"); + var int32 = Type.GetInt32Ty(context); + + var functionType = LLVMTypeRef.CreateFunction(int32.Handle, [int32.Handle], IsVarArg: false); + var function = module.AddFunction("f", (FunctionType)context.GetOrCreate(functionType)); + var entry = function.AppendBasicBlock("entry"); + var next = function.AppendBasicBlock("next"); + + using var builder = LLVMBuilderRef.Create(context.Handle); + builder.PositionAtEnd(entry.Handle); + + var addHandle = builder.BuildAdd(function.GetParam(0).Handle, function.GetParam(0).Handle, "sum"); + var retHandle = builder.BuildRet(addHandle); + + Assert.That(entry.Parent, Is.EqualTo(function)); + Assert.That(entry.Terminator, Is.EqualTo(context.GetOrCreate(retHandle))); + Assert.That(entry.FirstInstruction, Is.EqualTo(context.GetOrCreate(addHandle))); + Assert.That(entry.LastInstruction, Is.EqualTo(context.GetOrCreate(retHandle))); + Assert.That(entry.GetInstructions().Length, Is.EqualTo(2)); + Assert.That(entry.Next, Is.EqualTo(next)); + Assert.That(next.Previous, Is.EqualTo(entry)); + Assert.That(next.Terminator, Is.Null); + } } From f882098390a100d0aaceeb4b9e800436ee101007 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 15 Jul 2026 17:30:49 -0700 Subject: [PATCH 3/4] Expose instruction flags and derived operand accessors on the managed API Round-trips the instruction poison-generating and fast-math flags through the C API rather than the native helpers: HasNoSignedWrap/HasNoUnsignedWrap move onto LLVMGetNSW/GetNUW with setters, and Exact, disjoint, non-negative, GEP no-wrap, in-bounds, icmp same-sign, and fast-math flags are added, each guarded by the owning instruction kind so a release build never reads the wrong option bits. Also adds the trivially-derived accessors that need no new interop: CalledFunction, the GEP index/pointer operands, and the load/store pointer and value operands. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Extensions/LLVMValueRef.cs | 144 +++++++++++++++++- .../Users/Instructions/BinaryOperator.cs | 56 +++++++ .../Values/Users/Instructions/CallBase.cs | 2 + .../Users/Instructions/GetElementPtrInst.cs | 34 +++++ .../Values/Users/Instructions/ICmpInst.cs | 14 ++ .../Values/Users/Instructions/Instruction.cs | 14 ++ .../Values/Users/Instructions/LoadInst.cs | 2 + .../Values/Users/Instructions/StoreInst.cs | 4 + .../Values/Users/Instructions/ZExtInst.cs | 14 ++ tests/LLVMSharp.UnitTests/ManagedApi.cs | 54 +++++++ 10 files changed, 336 insertions(+), 2 deletions(-) diff --git a/sources/LLVMSharp.Interop/Extensions/LLVMValueRef.cs b/sources/LLVMSharp.Interop/Extensions/LLVMValueRef.cs index e41be9f0..805c0685 100644 --- a/sources/LLVMSharp.Interop/Extensions/LLVMValueRef.cs +++ b/sources/LLVMSharp.Interop/Extensions/LLVMValueRef.cs @@ -152,6 +152,38 @@ public readonly LLVMDLLStorageClass DLLStorageClass public readonly LLVMBasicBlockRef EntryBasicBlock => ((IsAFunction != null) && (BasicBlocksCount != 0)) ? LLVM.GetEntryBasicBlock(this) : default; + public readonly bool Exact + { + get + { + return InstructionOpcode is LLVMOpcode.LLVMUDiv or LLVMOpcode.LLVMSDiv or LLVMOpcode.LLVMLShr or LLVMOpcode.LLVMAShr && LLVM.GetExact(this) != 0; + } + + set + { + if (InstructionOpcode is LLVMOpcode.LLVMUDiv or LLVMOpcode.LLVMSDiv or LLVMOpcode.LLVMLShr or LLVMOpcode.LLVMAShr) + { + LLVM.SetExact(this, value ? 1 : 0); + } + } + } + + public readonly LLVMFastMathFlags FastMathFlags + { + get + { + return (LLVM.CanValueUseFastMathFlags(this) != 0) ? (LLVMFastMathFlags)LLVM.GetFastMathFlags(this) : default; + } + + set + { + if (LLVM.CanValueUseFastMathFlags(this) != 0) + { + LLVM.SetFastMathFlags(this, (uint)value); + } + } + } + public readonly LLVMRealPredicate FCmpPredicate => (Handle != IntPtr.Zero) ? LLVM.GetFCmpPredicate(this) : default; public readonly LLVMBasicBlockRef FirstBasicBlock => (IsAFunction != null) ? LLVM.GetFirstBasicBlock(this) : default; @@ -201,6 +233,22 @@ public readonly string GC } } + public readonly LLVMGEPNoWrapFlags GEPNoWrapFlags + { + get + { + return (IsAGetElementPtrInst != null) ? (LLVMGEPNoWrapFlags)LLVM.GEPGetNoWrapFlags(this) : default; + } + + set + { + if (IsAGetElementPtrInst != null) + { + LLVM.GEPSetNoWrapFlags(this, (uint)value); + } + } + } + public readonly LLVMTypeRef GEPSourceElementType => (IsAGetElementPtrInst != null) ? LLVM.GetGEPSourceElementType(this) : default; public readonly LLVMValueRef GlobalIFuncResolver @@ -224,9 +272,37 @@ public readonly LLVMValueRef GlobalIFuncResolver public readonly bool HasMetadata => (IsAInstruction != null) && LLVM.HasMetadata(this) != 0; - public readonly bool HasNoSignedWrap => (IsAInstruction != null) && llvmsharp.Instruction_hasNoSignedWrap(this) != 0; + public readonly bool HasNoSignedWrap + { + get + { + return InstructionOpcode is LLVMOpcode.LLVMAdd or LLVMOpcode.LLVMSub or LLVMOpcode.LLVMMul or LLVMOpcode.LLVMShl && LLVM.GetNSW(this) != 0; + } + + set + { + if (InstructionOpcode is LLVMOpcode.LLVMAdd or LLVMOpcode.LLVMSub or LLVMOpcode.LLVMMul or LLVMOpcode.LLVMShl) + { + LLVM.SetNSW(this, value ? 1 : 0); + } + } + } - public readonly bool HasNoUnsignedWrap => (IsAInstruction != null) && llvmsharp.Instruction_hasNoUnsignedWrap(this) != 0; + public readonly bool HasNoUnsignedWrap + { + get + { + return InstructionOpcode is LLVMOpcode.LLVMAdd or LLVMOpcode.LLVMSub or LLVMOpcode.LLVMMul or LLVMOpcode.LLVMShl && LLVM.GetNUW(this) != 0; + } + + set + { + if (InstructionOpcode is LLVMOpcode.LLVMAdd or LLVMOpcode.LLVMSub or LLVMOpcode.LLVMMul or LLVMOpcode.LLVMShl) + { + LLVM.SetNUW(this, value ? 1 : 0); + } + } + } public readonly bool HasPersonalityFn => (IsAFunction != null) && LLVM.HasPersonalityFn(this) != 0; @@ -245,6 +321,22 @@ public readonly bool HasUnnamedAddr public readonly LLVMIntPredicate ICmpPredicate => (Handle != IntPtr.Zero) ? LLVM.GetICmpPredicate(this) : default; + public readonly bool ICmpSameSign + { + get + { + return (IsAICmpInst != null) && LLVM.GetICmpSameSign(this) != 0; + } + + set + { + if (IsAICmpInst != null) + { + LLVM.SetICmpSameSign(this, value ? 1 : 0); + } + } + } + public readonly uint IncomingCount => (IsAPHINode != null) ? LLVM.CountIncoming(this) : default; public readonly LLVMValueRef Initializer @@ -489,6 +581,22 @@ public readonly bool IsCleanup public readonly bool IsDeclaration => (IsAGlobalValue != null) && LLVM.IsDeclaration(this) != 0; + public readonly bool IsDisjoint + { + get + { + return InstructionOpcode is LLVMOpcode.LLVMOr && LLVM.GetIsDisjoint(this) != 0; + } + + set + { + if (InstructionOpcode is LLVMOpcode.LLVMOr) + { + LLVM.SetIsDisjoint(this, value ? 1 : 0); + } + } + } + public readonly bool IsExternallyInitialized { get @@ -515,6 +623,22 @@ public readonly bool IsGlobalConstant } } + public readonly bool IsInBounds + { + get + { + return (IsAGetElementPtrInst != null) && LLVM.IsInBounds(this) != 0; + } + + set + { + if (IsAGetElementPtrInst != null) + { + LLVM.SetIsInBounds(this, value ? 1 : 0); + } + } + } + public readonly bool IsNull => (Handle != IntPtr.Zero) && LLVM.IsNull(this) != 0; public readonly bool IsPoison => (Handle != IntPtr.Zero) && LLVM.IsPoison(this) != 0; @@ -606,6 +730,22 @@ public readonly string Name public readonly LLVMValueRef NextParam => (IsAArgument != null) ? LLVM.GetNextParam(this) : default; + public readonly bool NNeg + { + get + { + return InstructionOpcode is LLVMOpcode.LLVMZExt or LLVMOpcode.LLVMUIToFP && LLVM.GetNNeg(this) != 0; + } + + set + { + if (InstructionOpcode is LLVMOpcode.LLVMZExt or LLVMOpcode.LLVMUIToFP) + { + LLVM.SetNNeg(this, value ? 1 : 0); + } + } + } + public readonly LLVMBasicBlockRef NormalDest => (IsAInvokeInst != null) ? LLVM.GetNormalDest(this) : default; public readonly uint NumClauses => (IsALandingPadInst != null) ? LLVM.GetNumClauses(this) : default; diff --git a/sources/LLVMSharp/Values/Users/Instructions/BinaryOperator.cs b/sources/LLVMSharp/Values/Users/Instructions/BinaryOperator.cs index 526bc2ce..ca9add8d 100644 --- a/sources/LLVMSharp/Values/Users/Instructions/BinaryOperator.cs +++ b/sources/LLVMSharp/Values/Users/Instructions/BinaryOperator.cs @@ -9,4 +9,60 @@ public sealed class BinaryOperator : Instruction internal BinaryOperator(LLVMValueRef handle) : base(handle.IsABinaryOperator) { } + + public bool HasNoSignedWrap + { + get + { + return Handle.HasNoSignedWrap; + } + + set + { + var handle = Handle; + handle.HasNoSignedWrap = value; + } + } + + public bool HasNoUnsignedWrap + { + get + { + return Handle.HasNoUnsignedWrap; + } + + set + { + var handle = Handle; + handle.HasNoUnsignedWrap = value; + } + } + + public bool IsDisjoint + { + get + { + return Handle.IsDisjoint; + } + + set + { + var handle = Handle; + handle.IsDisjoint = value; + } + } + + public bool IsExact + { + get + { + return Handle.Exact; + } + + set + { + var handle = Handle; + handle.Exact = value; + } + } } diff --git a/sources/LLVMSharp/Values/Users/Instructions/CallBase.cs b/sources/LLVMSharp/Values/Users/Instructions/CallBase.cs index 6d412cae..c1e0edb4 100644 --- a/sources/LLVMSharp/Values/Users/Instructions/CallBase.cs +++ b/sources/LLVMSharp/Values/Users/Instructions/CallBase.cs @@ -48,6 +48,8 @@ public Attribute[] GetAttributesAtIndex(LLVMAttributeIndex index) public FunctionType CalledFunctionType => Context.GetOrCreate(Handle.CalledFunctionType); + public Function? CalledFunction => CalledOperand as Function; + public Value CalledOperand => Context.GetOrCreate(Handle.CalledValue); public Value GetArgOperand(uint index) => Context.GetOrCreate(Handle.GetArgOperand(index)); diff --git a/sources/LLVMSharp/Values/Users/Instructions/GetElementPtrInst.cs b/sources/LLVMSharp/Values/Users/Instructions/GetElementPtrInst.cs index 5345a4a4..cd75da6b 100644 --- a/sources/LLVMSharp/Values/Users/Instructions/GetElementPtrInst.cs +++ b/sources/LLVMSharp/Values/Users/Instructions/GetElementPtrInst.cs @@ -10,5 +10,39 @@ internal GetElementPtrInst(LLVMValueRef handle) : base(handle.IsAGetElementPtrIn { } + public bool IsInBounds + { + get + { + return Handle.IsInBounds; + } + + set + { + var handle = Handle; + handle.IsInBounds = value; + } + } + + public LLVMGEPNoWrapFlags NoWrapFlags + { + get + { + return Handle.GEPNoWrapFlags; + } + + set + { + var handle = Handle; + handle.GEPNoWrapFlags = value; + } + } + + public uint NumIndices => NumOperands - 1; + + public Value PointerOperand => GetOperand(0); + + public Type PointerOperandType => PointerOperand.Type; + public Type SourceElementType => Context.GetOrCreate(Handle.GEPSourceElementType); } diff --git a/sources/LLVMSharp/Values/Users/Instructions/ICmpInst.cs b/sources/LLVMSharp/Values/Users/Instructions/ICmpInst.cs index 630bcbbf..beb1ad86 100644 --- a/sources/LLVMSharp/Values/Users/Instructions/ICmpInst.cs +++ b/sources/LLVMSharp/Values/Users/Instructions/ICmpInst.cs @@ -9,4 +9,18 @@ public sealed class ICmpInst : CmpInst internal ICmpInst(LLVMValueRef handle) : base(handle.IsAICmpInst) { } + + public bool HasSameSign + { + get + { + return Handle.ICmpSameSign; + } + + set + { + var handle = Handle; + handle.ICmpSameSign = value; + } + } } diff --git a/sources/LLVMSharp/Values/Users/Instructions/Instruction.cs b/sources/LLVMSharp/Values/Users/Instructions/Instruction.cs index 4888444f..94ab3ee2 100644 --- a/sources/LLVMSharp/Values/Users/Instructions/Instruction.cs +++ b/sources/LLVMSharp/Values/Users/Instructions/Instruction.cs @@ -11,6 +11,20 @@ private protected Instruction(LLVMValueRef handle) : base(handle.IsAInstruction, { } + public LLVMFastMathFlags FastMathFlags + { + get + { + return Handle.FastMathFlags; + } + + set + { + var handle = Handle; + handle.FastMathFlags = value; + } + } + public bool IsTerminator => Handle.IsATerminatorInst != null; public LLVMOpcode Opcode => Handle.InstructionOpcode; diff --git a/sources/LLVMSharp/Values/Users/Instructions/LoadInst.cs b/sources/LLVMSharp/Values/Users/Instructions/LoadInst.cs index 74147608..fa8359f0 100644 --- a/sources/LLVMSharp/Values/Users/Instructions/LoadInst.cs +++ b/sources/LLVMSharp/Values/Users/Instructions/LoadInst.cs @@ -50,4 +50,6 @@ public bool Volatile handle.Volatile = value; } } + + public Value PointerOperand => GetOperand(0); } diff --git a/sources/LLVMSharp/Values/Users/Instructions/StoreInst.cs b/sources/LLVMSharp/Values/Users/Instructions/StoreInst.cs index d4559da1..e7e4c12a 100644 --- a/sources/LLVMSharp/Values/Users/Instructions/StoreInst.cs +++ b/sources/LLVMSharp/Values/Users/Instructions/StoreInst.cs @@ -50,4 +50,8 @@ public bool Volatile handle.Volatile = value; } } + + public Value PointerOperand => GetOperand(1); + + public Value ValueOperand => GetOperand(0); } diff --git a/sources/LLVMSharp/Values/Users/Instructions/ZExtInst.cs b/sources/LLVMSharp/Values/Users/Instructions/ZExtInst.cs index 44eb4b3e..8c28ed96 100644 --- a/sources/LLVMSharp/Values/Users/Instructions/ZExtInst.cs +++ b/sources/LLVMSharp/Values/Users/Instructions/ZExtInst.cs @@ -9,4 +9,18 @@ public sealed class ZExtInst : CastInst internal ZExtInst(LLVMValueRef handle) : base(handle.IsAZExtInst) { } + + public bool IsNonNeg + { + get + { + return Handle.NNeg; + } + + set + { + var handle = Handle; + handle.NNeg = value; + } + } } diff --git a/tests/LLVMSharp.UnitTests/ManagedApi.cs b/tests/LLVMSharp.UnitTests/ManagedApi.cs index 4f6ed3ed..c6200a68 100644 --- a/tests/LLVMSharp.UnitTests/ManagedApi.cs +++ b/tests/LLVMSharp.UnitTests/ManagedApi.cs @@ -473,4 +473,58 @@ public void BasicBlockAccessors() Assert.That(next.Previous, Is.EqualTo(entry)); Assert.That(next.Terminator, Is.Null); } + + [Test] + public void InstructionFlagAccessors() + { + var context = new LLVMContext(); + var module = Module.Create(context, "m"); + var int32 = Type.GetInt32Ty(context); + var int64 = Type.GetInt64Ty(context); + var flt = Type.GetFloatTy(context); + + var functionType = LLVMTypeRef.CreateFunction(Type.GetVoidTy(context).Handle, [int32.Handle, int32.Handle, flt.Handle, flt.Handle], IsVarArg: false); + var function = module.AddFunction("f", (FunctionType)context.GetOrCreate(functionType)); + var entry = function.AppendBasicBlock("entry"); + + using var builder = LLVMBuilderRef.Create(context.Handle); + builder.PositionAtEnd(entry.Handle); + + var lhs = function.GetParam(0).Handle; + var rhs = function.GetParam(1).Handle; + + var add = (BinaryOperator)context.GetOrCreate(builder.BuildAdd(lhs, rhs, "add")); + add.HasNoSignedWrap = true; + add.HasNoUnsignedWrap = true; + Assert.That(add.HasNoSignedWrap, Is.True); + Assert.That(add.HasNoUnsignedWrap, Is.True); + + var div = (BinaryOperator)context.GetOrCreate(builder.BuildUDiv(lhs, rhs, "div")); + div.IsExact = true; + Assert.That(div.IsExact, Is.True); + + var disjoint = (BinaryOperator)context.GetOrCreate(builder.BuildOr(lhs, rhs, "or")); + disjoint.IsDisjoint = true; + Assert.That(disjoint.IsDisjoint, Is.True); + + var zext = (ZExtInst)context.GetOrCreate(builder.BuildZExt(lhs, int64.Handle, "zext")); + zext.IsNonNeg = true; + Assert.That(zext.IsNonNeg, Is.True); + + var icmp = (ICmpInst)context.GetOrCreate(builder.BuildICmp(LLVMIntPredicate.LLVMIntSLT, lhs, rhs, "cmp")); + icmp.HasSameSign = true; + Assert.That(icmp.HasSameSign, Is.True); + + var fadd = (Instruction)context.GetOrCreate(builder.BuildFAdd(function.GetParam(2).Handle, function.GetParam(3).Handle, "fadd")); + fadd.FastMathFlags = LLVMFastMathFlags.LLVMFastMathAll; + Assert.That(fadd.FastMathFlags, Is.EqualTo(LLVMFastMathFlags.LLVMFastMathAll)); + + var arrayType = LLVMTypeRef.CreateArray(int32.Handle, 4); + var storage = builder.BuildAlloca(arrayType, "arr"); + var gep = (GetElementPtrInst)context.GetOrCreate(builder.BuildGEP2(arrayType, storage, new[] { LLVMValueRef.CreateConstInt(int32.Handle, 0), LLVMValueRef.CreateConstInt(int32.Handle, 1) }, "elem")); + gep.IsInBounds = true; + Assert.That(gep.IsInBounds, Is.True); + Assert.That(gep.NumIndices, Is.EqualTo(2u)); + Assert.That(gep.PointerOperand, Is.EqualTo(context.GetOrCreate(storage))); + } } From 925fa5ca6cf356dbb493213d7e6863140b50dcd6 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 15 Jul 2026 17:31:06 -0700 Subject: [PATCH 4/4] Flesh out the managed Target, TargetMachine, and DataLayout types Fills the target codegen wrappers to mirror the C++ surface: Target gains description and JIT/target-machine/asm-backend queries and target enumeration; TargetMachine gains CPU, feature string, target, and triple; DataLayout gains a target-data constructor plus call-frame alignment, element offset, and the element-containing-offset query. Adds the matching getters on LLVMTargetRef and LLVMTargetMachineRef. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Extensions/LLVMTargetMachineRef.cs | 53 +++++++++++++++++++ .../Extensions/LLVMTargetRef.cs | 26 +++++++++ sources/LLVMSharp/DataLayout.cs | 44 ++++++++++++--- sources/LLVMSharp/Target.cs | 48 +++++++++++++++++ sources/LLVMSharp/TargetMachine.cs | 27 ++++++++++ tests/LLVMSharp.UnitTests/ManagedApi.cs | 37 +++++++++++++ 6 files changed, 227 insertions(+), 8 deletions(-) diff --git a/sources/LLVMSharp.Interop/Extensions/LLVMTargetMachineRef.cs b/sources/LLVMSharp.Interop/Extensions/LLVMTargetMachineRef.cs index 1d3c8a9e..a16663f3 100644 --- a/sources/LLVMSharp.Interop/Extensions/LLVMTargetMachineRef.cs +++ b/sources/LLVMSharp.Interop/Extensions/LLVMTargetMachineRef.cs @@ -17,6 +17,59 @@ public unsafe partial struct LLVMTargetMachineRef(IntPtr handle) : IEquatable !(left == right); + public readonly string CPU + { + get + { + var pCPU = LLVM.GetTargetMachineCPU(this); + + if (pCPU == null) + { + return string.Empty; + } + + var result = SpanExtensions.AsString(pCPU); + LLVM.DisposeMessage(pCPU); + return result; + } + } + + public readonly string FeatureString + { + get + { + var pFeatureString = LLVM.GetTargetMachineFeatureString(this); + + if (pFeatureString == null) + { + return string.Empty; + } + + var result = SpanExtensions.AsString(pFeatureString); + LLVM.DisposeMessage(pFeatureString); + return result; + } + } + + public readonly LLVMTargetRef Target => (Handle != IntPtr.Zero) ? LLVM.GetTargetMachineTarget(this) : default; + + public readonly string Triple + { + get + { + var pTriple = LLVM.GetTargetMachineTriple(this); + + if (pTriple == null) + { + return string.Empty; + } + + var result = SpanExtensions.AsString(pTriple); + LLVM.DisposeMessage(pTriple); + return result; + } + } + public readonly LLVMTargetDataRef CreateTargetDataLayout() => LLVM.CreateTargetDataLayout(this); public readonly void EmitToFile(LLVMModuleRef module, string fileName, LLVMCodeGenFileType codegen) => EmitToFile(module, fileName.AsSpan(), codegen); diff --git a/sources/LLVMSharp.Interop/Extensions/LLVMTargetRef.cs b/sources/LLVMSharp.Interop/Extensions/LLVMTargetRef.cs index 042ce323..907cbf44 100644 --- a/sources/LLVMSharp.Interop/Extensions/LLVMTargetRef.cs +++ b/sources/LLVMSharp.Interop/Extensions/LLVMTargetRef.cs @@ -75,6 +75,32 @@ public static IEnumerable Targets } } + public readonly string Description + { + get + { + if (Handle == IntPtr.Zero) + { + return string.Empty; + } + + var pDescription = LLVM.GetTargetDescription(this); + + if (pDescription == null) + { + return string.Empty; + } + + return SpanExtensions.AsString(pDescription); + } + } + + public readonly bool HasAsmBackend => (Handle != IntPtr.Zero) && LLVM.TargetHasAsmBackend(this) != 0; + + public readonly bool HasJIT => (Handle != IntPtr.Zero) && LLVM.TargetHasJIT(this) != 0; + + public readonly bool HasTargetMachine => (Handle != IntPtr.Zero) && LLVM.TargetHasTargetMachine(this) != 0; + public readonly string Name { get diff --git a/sources/LLVMSharp/DataLayout.cs b/sources/LLVMSharp/DataLayout.cs index e0a72f20..aa2184b3 100644 --- a/sources/LLVMSharp/DataLayout.cs +++ b/sources/LLVMSharp/DataLayout.cs @@ -5,12 +5,46 @@ namespace LLVMSharp; -public sealed class DataLayout(ReadOnlySpan stringRep) : IEquatable +public sealed class DataLayout : IEquatable { - public LLVMTargetDataRef Handle { get; } = LLVMTargetDataRef.FromStringRepresentation(stringRep); + public DataLayout(ReadOnlySpan stringRep) + { + Handle = LLVMTargetDataRef.FromStringRepresentation(stringRep); + } + + internal DataLayout(LLVMTargetDataRef handle) + { + Handle = handle; + } + + public LLVMTargetDataRef Handle { get; } public StructLayout GetStructLayout(StructType structType) => new StructLayout(this, structType); + public uint GetABITypeAlignment(Type type) + { + ArgumentNullException.ThrowIfNull(type); + return Handle.ABIAlignmentOfType(type.Handle); + } + + public uint GetCallFrameTypeAlignment(Type type) + { + ArgumentNullException.ThrowIfNull(type); + return Handle.CallFrameAlignmentOfType(type.Handle); + } + + public uint GetElementContainingOffset(StructType structType, ulong offset) + { + ArgumentNullException.ThrowIfNull(structType); + return (uint)Handle.ElementAtOffset(structType.Handle, offset); + } + + public ulong GetOffsetOfElement(StructType structType, uint element) + { + ArgumentNullException.ThrowIfNull(structType); + return Handle.OffsetOfElement(structType.Handle, element); + } + public ulong GetTypeSizeInBits(Type type) { ArgumentNullException.ThrowIfNull(type); @@ -29,12 +63,6 @@ public ulong GetTypeAllocSize(Type type) return Handle.ABISizeOfType(type.Handle); } - public uint GetABITypeAlignment(Type type) - { - ArgumentNullException.ThrowIfNull(type); - return Handle.ABIAlignmentOfType(type.Handle); - } - public uint GetPrefTypeAlignment(Type type) { ArgumentNullException.ThrowIfNull(type); diff --git a/sources/LLVMSharp/Target.cs b/sources/LLVMSharp/Target.cs index 5149b95a..7b8fba5f 100644 --- a/sources/LLVMSharp/Target.cs +++ b/sources/LLVMSharp/Target.cs @@ -1,14 +1,62 @@ // Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. using System; +using System.Collections.Generic; using LLVMSharp.Interop; namespace LLVMSharp; public sealed class Target : IEquatable { + internal Target(LLVMTargetRef handle) + { + Handle = handle; + } + public LLVMTargetRef Handle { get; } + public static string DefaultTriple => LLVMTargetRef.DefaultTriple; + + public static IEnumerable Targets + { + get + { + foreach (var handle in LLVMTargetRef.Targets) + { + yield return new Target(handle); + } + } + } + + public string Description => Handle.Description; + + public bool HasAsmBackend => Handle.HasAsmBackend; + + public bool HasJIT => Handle.HasJIT; + + public bool HasTargetMachine => Handle.HasTargetMachine; + + public string Name => Handle.Name; + + public static Target GetTargetFromTriple(string triple) => new Target(LLVMTargetRef.GetTargetFromTriple(triple)); + + public static bool TryGetTargetFromTriple(string triple, out Target target, out string error) + { + if (LLVMTargetRef.TryGetTargetFromTriple(triple.AsSpan(), out var handle, out error)) + { + target = new Target(handle); + return true; + } + + target = null!; + return false; + } + + public TargetMachine CreateTargetMachine(string triple, string cpu, string features, LLVMCodeGenOptLevel level, LLVMRelocMode reloc, LLVMCodeModel codeModel) + { + return new TargetMachine(Handle.CreateTargetMachine(triple, cpu, features, level, reloc, codeModel)); + } + public static bool operator ==(Target? left, Target? right) => ReferenceEquals(left, right) || (left?.Handle == right?.Handle); public static bool operator !=(Target? left, Target? right) => !(left == right); diff --git a/sources/LLVMSharp/TargetMachine.cs b/sources/LLVMSharp/TargetMachine.cs index 90253a1a..a5c90867 100644 --- a/sources/LLVMSharp/TargetMachine.cs +++ b/sources/LLVMSharp/TargetMachine.cs @@ -7,8 +7,35 @@ namespace LLVMSharp; public sealed class TargetMachine : IEquatable { + internal TargetMachine(LLVMTargetMachineRef handle) + { + Handle = handle; + } + public LLVMTargetMachineRef Handle { get; } + public string CPU => Handle.CPU; + + public string FeatureString => Handle.FeatureString; + + public Target Target => new Target(Handle.Target); + + public string Triple => Handle.Triple; + + public DataLayout CreateTargetDataLayout() => new DataLayout(Handle.CreateTargetDataLayout()); + + public void EmitToFile(Module module, string fileName, LLVMCodeGenFileType codegen) + { + ArgumentNullException.ThrowIfNull(module); + Handle.EmitToFile(module.Handle, fileName, codegen); + } + + public bool TryEmitToFile(Module module, string fileName, LLVMCodeGenFileType codegen, out string message) + { + ArgumentNullException.ThrowIfNull(module); + return Handle.TryEmitToFile(module.Handle, fileName, codegen, out message); + } + public static bool operator ==(TargetMachine? left, TargetMachine? right) => ReferenceEquals(left, right) || (left?.Handle == right?.Handle); public static bool operator !=(TargetMachine? left, TargetMachine? right) => !(left == right); diff --git a/tests/LLVMSharp.UnitTests/ManagedApi.cs b/tests/LLVMSharp.UnitTests/ManagedApi.cs index c6200a68..0b3df1dc 100644 --- a/tests/LLVMSharp.UnitTests/ManagedApi.cs +++ b/tests/LLVMSharp.UnitTests/ManagedApi.cs @@ -527,4 +527,41 @@ public void InstructionFlagAccessors() Assert.That(gep.NumIndices, Is.EqualTo(2u)); Assert.That(gep.PointerOperand, Is.EqualTo(context.GetOrCreate(storage))); } + + [Test] + public void TargetAndTargetMachineAccessors() + { + LLVM.InitializeAllTargetInfos(); + LLVM.InitializeAllTargets(); + LLVM.InitializeAllTargetMCs(); + + var triple = Target.DefaultTriple; + Assert.That(triple, Is.Not.Empty); + + var target = Target.GetTargetFromTriple(triple); + Assert.That(target.Name, Is.Not.Empty); + Assert.That(target.Description, Is.Not.Null); + + var targetMachine = target.CreateTargetMachine(triple, "", "", LLVMCodeGenOptLevel.LLVMCodeGenLevelDefault, LLVMRelocMode.LLVMRelocDefault, LLVMCodeModel.LLVMCodeModelDefault); + Assert.That(targetMachine.Triple, Is.EqualTo(triple)); + Assert.That(targetMachine.Target, Is.EqualTo(target)); + + var dataLayout = targetMachine.CreateTargetDataLayout(); + var context = new LLVMContext(); + Assert.That(dataLayout.GetTypeSizeInBits(Type.GetInt32Ty(context)), Is.EqualTo(32ul)); + } + + [Test] + public void DataLayoutAccessors() + { + var context = new LLVMContext(); + var dataLayout = new DataLayout("e-m:w-p:64:64-i64:64-n8:16:32:64"); + + var structType = StructType.Create(context, "S"); + structType.SetBody([Type.GetInt32Ty(context), Type.GetInt64Ty(context)], packed: false); + + Assert.That(dataLayout.GetTypeSizeInBits(Type.GetInt64Ty(context)), Is.EqualTo(64ul)); + Assert.That(dataLayout.GetOffsetOfElement(structType, 1), Is.EqualTo(8ul)); + Assert.That(dataLayout.GetElementContainingOffset(structType, 8), Is.EqualTo(1u)); + } }