[llvm] [llvm-c] Add LLVMParseIRInContext2 (PR #174085)

Tamir Duberstein via llvm-commits llvm-commits at lists.llvm.org
Thu Jan 8 08:18:31 PST 2026


https://github.com/tamird updated https://github.com/llvm/llvm-project/pull/174085

>From 53fffc7ff917482a1393e28748359f03d2a5554a Mon Sep 17 00:00:00 2001
From: Tamir Duberstein <tamird at gmail.com>
Date: Wed, 31 Dec 2025 05:56:45 -0500
Subject: [PATCH] [llvm-c] Add LLVMParseIRInContext2

This new function is the same as LLVMParseIRInContext except it doesn't
take ownership of the memory buffer. This fixes a wart that has been in
place since 5ebb7b311223bcd21d2b3d25413d1edacefcc63d changed the
underlying internal API to avoid taking ownership.

Reduce nesting in the implementation of LLVMParseIRInContext (now
LLVMParseIRInContext2) as well.

Update examples, OCaml bindings, and tests including plugging some
pre-existing memory leaks. OCaml bindings have renamed `parse_ir` to
`parse_ir_bitcode_or_assembly` to provoke compilation failures in
downstream code; this is intentional as this function now requires the
memory buffer to be disposed by the caller.
---
 .../ocaml/bitreader/llvm_bitreader.mli        | 15 ++++++-
 llvm/bindings/ocaml/irreader/irreader_ocaml.c |  5 +--
 llvm/bindings/ocaml/irreader/llvm_irreader.ml |  5 ++-
 .../bindings/ocaml/irreader/llvm_irreader.mli | 16 +++++---
 llvm/docs/ReleaseNotes.md                     | 10 +++++
 .../OrcV2CBindingsLazy/OrcV2CBindingsLazy.c   | 17 ++++----
 .../OrcV2CBindingsVeryLazy.c                  | 12 +++---
 llvm/include/llvm-c/IRReader.h                | 17 ++++++++
 llvm/include/llvm-c/Object.h                  |  4 +-
 .../llvm/Analysis/LoopAnalysisManager.h       |  4 +-
 .../include/llvm/CodeGen/MachinePassManager.h |  4 +-
 llvm/include/llvm/IR/PassManager.h            |  4 +-
 llvm/lib/IRReader/IRReader.cpp                | 30 +++++++-------
 llvm/test/Bindings/OCaml/bitreader.ml         | 20 ++++++----
 llvm/test/Bindings/OCaml/irreader.ml          | 39 ++++++++++++++-----
 15 files changed, 140 insertions(+), 62 deletions(-)

diff --git a/llvm/bindings/ocaml/bitreader/llvm_bitreader.mli b/llvm/bindings/ocaml/bitreader/llvm_bitreader.mli
index def8b84fe9e62..9133c3993c1cc 100644
--- a/llvm/bindings/ocaml/bitreader/llvm_bitreader.mli
+++ b/llvm/bindings/ocaml/bitreader/llvm_bitreader.mli
@@ -16,11 +16,22 @@ exception Error of string
 (** [get_module context mb] reads the bitcode for a new module [m] from the
     memory buffer [mb] in the context [context].  Returns [m] if successful, or
     raises [Error msg] otherwise, where [msg] is a description of the error
-    encountered. See the function [llvm::getBitcodeModule]. *)
+    encountered.
+
+    If parsing succeeds, ownership of [mb] is transferred to the returned
+    module (for lazy deserialization); the caller must not dispose [mb]. If
+    parsing fails, ownership is retained by the caller, which must dispose it.
+
+    See the function [llvm::getBitcodeModule]. *)
 val get_module : Llvm.llcontext -> Llvm.llmemorybuffer -> Llvm.llmodule
 
 (** [parse_bitcode context mb] parses the bitcode for a new module [m] from the
     memory buffer [mb] in the context [context]. Returns [m] if successful, or
     raises [Error msg] otherwise, where [msg] is a description of the error
-    encountered. See the function [llvm::ParseBitcodeFile]. *)
+    encountered.
+
+    This function does not take ownership of [mb]; the caller should dispose it
+    (see {!Llvm.MemoryBuffer.dispose}) when it is no longer needed.
+
+    See the function [llvm::ParseBitcodeFile]. *)
 val parse_bitcode : Llvm.llcontext -> Llvm.llmemorybuffer -> Llvm.llmodule
diff --git a/llvm/bindings/ocaml/irreader/irreader_ocaml.c b/llvm/bindings/ocaml/irreader/irreader_ocaml.c
index 708d6fb5b0b79..594f490a49a95 100644
--- a/llvm/bindings/ocaml/irreader/irreader_ocaml.c
+++ b/llvm/bindings/ocaml/irreader/irreader_ocaml.c
@@ -24,12 +24,11 @@ void llvm_raise(value Prototype, char *Message);
 /* Llvm.llcontext -> Llvm.llmemorybuffer -> Llvm.llmodule */
 value llvm_parse_ir(value C, value MemBuf) {
   CAMLparam0();
-  CAMLlocal2(Variant, MessageVal);
   LLVMModuleRef M;
   char *Message;
 
-  if (LLVMParseIRInContext(Context_val(C), MemoryBuffer_val(MemBuf), &M,
-                           &Message))
+  if (LLVMParseIRInContext2(Context_val(C), MemoryBuffer_val(MemBuf), &M,
+                            &Message))
     llvm_raise(*caml_named_value("Llvm_irreader.Error"), Message);
 
   CAMLreturn(to_val(M));
diff --git a/llvm/bindings/ocaml/irreader/llvm_irreader.ml b/llvm/bindings/ocaml/irreader/llvm_irreader.ml
index a8ece43318155..2a808369a9a10 100644
--- a/llvm/bindings/ocaml/irreader/llvm_irreader.ml
+++ b/llvm/bindings/ocaml/irreader/llvm_irreader.ml
@@ -11,5 +11,6 @@ exception Error of string
 
 let _ = Callback.register_exception "Llvm_irreader.Error" (Error "")
 
-external parse_ir : Llvm.llcontext -> Llvm.llmemorybuffer -> Llvm.llmodule
-                  = "llvm_parse_ir"
+external parse_ir_bitcode_or_assembly
+  : Llvm.llcontext -> Llvm.llmemorybuffer -> Llvm.llmodule
+  = "llvm_parse_ir"
diff --git a/llvm/bindings/ocaml/irreader/llvm_irreader.mli b/llvm/bindings/ocaml/irreader/llvm_irreader.mli
index bdb7d040845ab..9e7a10cc09c5a 100644
--- a/llvm/bindings/ocaml/irreader/llvm_irreader.mli
+++ b/llvm/bindings/ocaml/irreader/llvm_irreader.mli
@@ -13,8 +13,14 @@
 
 exception Error of string
 
-(** [parse_ir context mb] parses the IR for a new module [m] from the
-    memory buffer [mb] in the context [context]. Returns [m] if successful, or
-    raises [Error msg] otherwise, where [msg] is a description of the error
-    encountered. See the function [llvm::ParseIR]. *)
-val parse_ir : Llvm.llcontext -> Llvm.llmemorybuffer -> Llvm.llmodule
+(** [parse_ir_bitcode_or_assembly context mb] parses the IR for a new module [m]
+    from the memory buffer [mb] in the context [context]. Returns [m] if
+    successful, or raises [Error msg] otherwise, where [msg] is a description
+    of the error encountered.
+
+    This function does not take ownership of [mb]; the caller should dispose it
+    (see {!Llvm.MemoryBuffer.dispose}) when it is no longer needed.
+
+    See the function [llvm::ParseIR]. *)
+val parse_ir_bitcode_or_assembly
+  : Llvm.llcontext -> Llvm.llmemorybuffer -> Llvm.llmodule
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index b97448bcc9057..eb51bc1e44d94 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -182,6 +182,12 @@ Changes to the X86 Backend
 Changes to the OCaml bindings
 -----------------------------
 
+* The IR reader bindings renamed `parse_ir` to
+  `parse_ir_bitcode_or_assembly` to clarify that the parser accepts both
+  textual IR and bitcode. This rename is intentional to force existing code to
+  update because the ownership semantics changed: the function no longer takes
+  ownership of the input memory buffer.
+
 Changes to the Python bindings
 ------------------------------
 
@@ -191,6 +197,10 @@ Changes to the C API
 * Add `LLVMGetOrInsertFunction` to get or insert a function, replacing the combination of `LLVMGetNamedFunction` and `LLVMAddFunction`.
 * Allow `LLVMGetVolatile` to work with any kind of Instruction.
 * Add `LLVMConstFPFromBits` to get a constant floating-point value from an array of 64 bit values.
+* Add `LLVMParseIRInContext2`, which is equivalent to `LLVMParseIRInContext`
+  but does not take ownership of the input `LLVMMemoryBufferRef`. This matches
+  the underlying C++ API and avoids ownership surprises in language bindings
+  and examples.
 * Functions working on the global context have been deprecated. Use the
   functions that work on a specific context instead.
 
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/OrcV2CBindingsLazy.c b/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/OrcV2CBindingsLazy.c
index 9c31f93899201..ea753c6fcc224 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/OrcV2CBindingsLazy.c
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/OrcV2CBindingsLazy.c
@@ -71,16 +71,19 @@ LLVMErrorRef parseExampleModule(const char *Source, size_t Len,
   // Create an LLVMContext for the Module.
   LLVMContextRef Ctx = LLVMContextCreate();
 
-  // Wrap Source in a MemoryBuffer
-  LLVMMemoryBufferRef MB =
-      LLVMCreateMemoryBufferWithMemoryRange(Source, Len, Name, 0);
-
   // Parse the LLVM module.
   LLVMModuleRef M;
   char *ErrMsg;
-  if (LLVMParseIRInContext(Ctx, MB, &M, &ErrMsg)) {
-    return LLVMCreateStringError(ErrMsg);
-    // TODO: LLVMDisposeMessage(ErrMsg);
+  // Wrap Source in a MemoryBuffer.
+  LLVMMemoryBufferRef MB =
+      LLVMCreateMemoryBufferWithMemoryRange(Source, Len, Name, 0);
+  LLVMBool Ret = LLVMParseIRInContext2(Ctx, MB, &M, &ErrMsg);
+  LLVMDisposeMemoryBuffer(MB);
+
+  if (Ret) {
+    LLVMErrorRef Err = LLVMCreateStringError(ErrMsg);
+    LLVMDisposeMessage(ErrMsg);
+    return Err;
   }
 
   // Create a new ThreadSafeContext to hold the context.
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsVeryLazy/OrcV2CBindingsVeryLazy.c b/llvm/examples/OrcV2Examples/OrcV2CBindingsVeryLazy/OrcV2CBindingsVeryLazy.c
index c63a72f1470ff..50272a48925b4 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsVeryLazy/OrcV2CBindingsVeryLazy.c
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsVeryLazy/OrcV2CBindingsVeryLazy.c
@@ -77,14 +77,16 @@ LLVMErrorRef parseExampleModule(const char *Source, size_t Len,
   // Create an LLVMContext.
   LLVMContextRef Ctx = LLVMContextCreate();
 
-  // Wrap Source in a MemoryBuffer
-  LLVMMemoryBufferRef MB =
-      LLVMCreateMemoryBufferWithMemoryRange(Source, Len, Name, 1);
-
   // Parse the LLVM module.
   LLVMModuleRef M;
   char *ErrMsg;
-  if (LLVMParseIRInContext(Ctx, MB, &M, &ErrMsg)) {
+  // Wrap Source in a MemoryBuffer.
+  LLVMMemoryBufferRef MB =
+      LLVMCreateMemoryBufferWithMemoryRange(Source, Len, Name, 0);
+  LLVMBool Ret = LLVMParseIRInContext2(Ctx, MB, &M, &ErrMsg);
+  LLVMDisposeMemoryBuffer(MB);
+
+  if (Ret) {
     LLVMErrorRef Err = LLVMCreateStringError(ErrMsg);
     LLVMDisposeMessage(ErrMsg);
     return Err;
diff --git a/llvm/include/llvm-c/IRReader.h b/llvm/include/llvm-c/IRReader.h
index 9e5615b5be596..4818ad780d48a 100644
--- a/llvm/include/llvm-c/IRReader.h
+++ b/llvm/include/llvm-c/IRReader.h
@@ -33,6 +33,8 @@ LLVM_C_EXTERN_C_BEGIN
  * Optionally returns a human-readable description of any errors that
  * occurred during parsing IR. OutMessage must be disposed with
  * LLVMDisposeMessage.
+ * The memory buffer is consumed by this function.
+ * This is deprecated. Use LLVMParseIRInContext2 instead.
  *
  * @see llvm::ParseIR()
  */
@@ -40,6 +42,21 @@ LLVM_C_ABI LLVMBool LLVMParseIRInContext(LLVMContextRef ContextRef,
                                          LLVMMemoryBufferRef MemBuf,
                                          LLVMModuleRef *OutM,
                                          char **OutMessage);
+/**
+ * Read LLVM IR from a memory buffer and convert it into an in-memory Module
+ * object. Returns 0 on success.
+ * Optionally returns a human-readable description of any errors that
+ * occurred during parsing IR. OutMessage must be disposed with
+ * LLVMDisposeMessage.
+ * The memory buffer is not consumed by this function. It is the responsibility
+ * of the caller to free it with \c LLVMDisposeMemoryBuffer.
+ *
+ * @see llvm::ParseIR()
+ */
+LLVM_C_ABI LLVMBool LLVMParseIRInContext2(LLVMContextRef ContextRef,
+                                          LLVMMemoryBufferRef MemBuf,
+                                          LLVMModuleRef *OutM,
+                                          char **OutMessage);
 
 /**
  * @}
diff --git a/llvm/include/llvm-c/Object.h b/llvm/include/llvm-c/Object.h
index f24f768570c5e..00ee254861805 100644
--- a/llvm/include/llvm-c/Object.h
+++ b/llvm/include/llvm-c/Object.h
@@ -66,7 +66,7 @@ typedef enum {
  * appropriate implementation selected.  The context may be NULL except if
  * the resulting file is an LLVM IR file.
  *
- * The memory buffer is not consumed by this function.  It is the responsibilty
+ * The memory buffer is not consumed by this function. It is the responsibility
  * of the caller to free it with \c LLVMDisposeMemoryBuffer.
  *
  * If NULL is returned, the \p ErrorMessage parameter is populated with the
@@ -82,7 +82,7 @@ LLVM_C_ABI LLVMBinaryRef LLVMCreateBinary(LLVMMemoryBufferRef MemBuf,
 /**
  * Dispose of a binary file.
  *
- * The binary file does not own its backing buffer.  It is the responsibilty
+ * The binary file does not own its backing buffer. It is the responsibility
  * of the caller to free it with \c LLVMDisposeMemoryBuffer.
  */
 LLVM_C_ABI void LLVMDisposeBinary(LLVMBinaryRef BR);
diff --git a/llvm/include/llvm/Analysis/LoopAnalysisManager.h b/llvm/include/llvm/Analysis/LoopAnalysisManager.h
index 1755257fe6c89..1c09889b80064 100644
--- a/llvm/include/llvm/Analysis/LoopAnalysisManager.h
+++ b/llvm/include/llvm/Analysis/LoopAnalysisManager.h
@@ -90,7 +90,7 @@ template <> class LoopAnalysisManagerFunctionProxy::Result {
   Result(Result &&Arg)
       : InnerAM(std::move(Arg.InnerAM)), LI(Arg.LI), MSSAUsed(Arg.MSSAUsed) {
     // We have to null out the analysis manager in the moved-from state
-    // because we are taking ownership of the responsibilty to clear the
+    // because we are taking ownership of the responsibility to clear the
     // analysis state.
     Arg.InnerAM = nullptr;
   }
@@ -99,7 +99,7 @@ template <> class LoopAnalysisManagerFunctionProxy::Result {
     LI = RHS.LI;
     MSSAUsed = RHS.MSSAUsed;
     // We have to null out the analysis manager in the moved-from state
-    // because we are taking ownership of the responsibilty to clear the
+    // because we are taking ownership of the responsibility to clear the
     // analysis state.
     RHS.InnerAM = nullptr;
     return *this;
diff --git a/llvm/include/llvm/CodeGen/MachinePassManager.h b/llvm/include/llvm/CodeGen/MachinePassManager.h
index 08bed33da2c31..f7b888e45a7ec 100644
--- a/llvm/include/llvm/CodeGen/MachinePassManager.h
+++ b/llvm/include/llvm/CodeGen/MachinePassManager.h
@@ -135,7 +135,7 @@ class FunctionAnalysisManagerMachineFunctionProxy
 
     Result(Result &&Arg) : FAM(std::move(Arg.FAM)) {
       // We have to null out the analysis manager in the moved-from state
-      // because we are taking ownership of the responsibilty to clear the
+      // because we are taking ownership of the responsibility to clear the
       // analysis state.
       Arg.FAM = nullptr;
     }
@@ -143,7 +143,7 @@ class FunctionAnalysisManagerMachineFunctionProxy
     Result &operator=(Result &&RHS) {
       FAM = RHS.FAM;
       // We have to null out the analysis manager in the moved-from state
-      // because we are taking ownership of the responsibilty to clear the
+      // because we are taking ownership of the responsibility to clear the
       // analysis state.
       RHS.FAM = nullptr;
       return *this;
diff --git a/llvm/include/llvm/IR/PassManager.h b/llvm/include/llvm/IR/PassManager.h
index 34a55a0ae4eb7..4d4c4bcfe943d 100644
--- a/llvm/include/llvm/IR/PassManager.h
+++ b/llvm/include/llvm/IR/PassManager.h
@@ -589,7 +589,7 @@ class LLVM_TEMPLATE_ABI InnerAnalysisManagerProxy
 
     Result(Result &&Arg) : InnerAM(std::move(Arg.InnerAM)) {
       // We have to null out the analysis manager in the moved-from state
-      // because we are taking ownership of the responsibilty to clear the
+      // because we are taking ownership of the responsibility to clear the
       // analysis state.
       Arg.InnerAM = nullptr;
     }
@@ -607,7 +607,7 @@ class LLVM_TEMPLATE_ABI InnerAnalysisManagerProxy
     Result &operator=(Result &&RHS) {
       InnerAM = RHS.InnerAM;
       // We have to null out the analysis manager in the moved-from state
-      // because we are taking ownership of the responsibilty to clear the
+      // because we are taking ownership of the responsibility to clear the
       // analysis state.
       RHS.InnerAM = nullptr;
       return *this;
diff --git a/llvm/lib/IRReader/IRReader.cpp b/llvm/lib/IRReader/IRReader.cpp
index c16871f081d1d..29bf18c01fd7c 100644
--- a/llvm/lib/IRReader/IRReader.cpp
+++ b/llvm/lib/IRReader/IRReader.cpp
@@ -17,6 +17,7 @@
 #include "llvm/Support/SourceMgr.h"
 #include "llvm/Support/Timer.h"
 #include "llvm/Support/raw_ostream.h"
+#include <cstring>
 #include <optional>
 #include <system_error>
 
@@ -117,23 +118,26 @@ std::unique_ptr<Module> llvm::parseIRFile(StringRef Filename, SMDiagnostic &Err,
 LLVMBool LLVMParseIRInContext(LLVMContextRef ContextRef,
                               LLVMMemoryBufferRef MemBuf, LLVMModuleRef *OutM,
                               char **OutMessage) {
-  SMDiagnostic Diag;
-
   std::unique_ptr<MemoryBuffer> MB(unwrap(MemBuf));
-  *OutM =
-      wrap(parseIR(MB->getMemBufferRef(), Diag, *unwrap(ContextRef)).release());
+  return LLVMParseIRInContext2(ContextRef, wrap(MB.get()), OutM, OutMessage);
+}
 
-  if(!*OutM) {
-    if (OutMessage) {
-      std::string buf;
-      raw_string_ostream os(buf);
+LLVMBool LLVMParseIRInContext2(LLVMContextRef ContextRef,
+                               LLVMMemoryBufferRef MemBuf, LLVMModuleRef *OutM,
+                               char **OutMessage) {
+  SMDiagnostic Diag;
 
-      Diag.print(nullptr, os, false);
+  *OutM = wrap(parseIR(*unwrap(MemBuf), Diag, *unwrap(ContextRef)).release());
 
-      *OutMessage = strdup(buf.c_str());
-    }
-    return 1;
+  if (*OutM)
+    return 0;
+
+  if (OutMessage) {
+    std::string Buf;
+    raw_string_ostream OS(Buf);
+    Diag.print(nullptr, OS, /*ShowColors=*/false);
+    *OutMessage = strdup(Buf.c_str());
   }
 
-  return 0;
+  return 1;
 }
diff --git a/llvm/test/Bindings/OCaml/bitreader.ml b/llvm/test/Bindings/OCaml/bitreader.ml
index 2638ca9d8c769..fa0f66532eb6d 100644
--- a/llvm/test/Bindings/OCaml/bitreader.ml
+++ b/llvm/test/Bindings/OCaml/bitreader.ml
@@ -16,6 +16,16 @@ let diagnostic_handler _ = ()
 
 let test x = if not x then exit 1 else ()
 
+(* TODO: Replace with Fun.protect when the minimum OCaml version supports it. *)
+let protect ~finally f =
+  try
+    let r = f () in
+    finally ();
+    r
+  with x ->
+    finally ();
+    raise x
+
 let _ =
   Llvm.set_diagnostic_handler context (Some diagnostic_handler);
 
@@ -29,13 +39,9 @@ let _ =
   (* parse_bitcode *)
   begin
     let mb = Llvm.MemoryBuffer.of_file fn in
-    begin try
-      let m = Llvm_bitreader.parse_bitcode context mb in
-      Llvm.dispose_module m
-    with x ->
-      Llvm.MemoryBuffer.dispose mb;
-      raise x
-    end
+    let m = protect ~finally:(fun () -> Llvm.MemoryBuffer.dispose mb)
+              (fun () -> Llvm_bitreader.parse_bitcode context mb) in
+    Llvm.dispose_module m
   end;
 
   (* MemoryBuffer.of_file *)
diff --git a/llvm/test/Bindings/OCaml/irreader.ml b/llvm/test/Bindings/OCaml/irreader.ml
index 7d8e4a97e38dc..ae11716fd5f4c 100644
--- a/llvm/test/Bindings/OCaml/irreader.ml
+++ b/llvm/test/Bindings/OCaml/irreader.ml
@@ -29,27 +29,46 @@ let _ =
 let insist cond =
   if not cond then failwith "insist"
 
+(* TODO: Replace with Fun.protect when the minimum OCaml version supports it. *)
+let protect ~finally f =
+  try
+    let r = f () in
+    finally ();
+    r
+  with x ->
+    finally ();
+    raise x
+
 
 (*===-- IR Reader ---------------------------------------------------------===*)
 
 let test_irreader () =
   begin
     let buf = MemoryBuffer.of_string "@foo = global i32 42" in
-    let m   = parse_ir context buf in
-    match lookup_global "foo" m with
-    | Some foo ->
-        insist ((global_initializer foo) = (Some (const_int (i32_type context) 42)))
-    | None ->
-        failwith "global"
+    let m = protect ~finally:(fun () -> MemoryBuffer.dispose buf)
+              (fun () -> parse_ir_bitcode_or_assembly context buf) in
+    protect ~finally:(fun () -> dispose_module m) (fun () ->
+      match lookup_global "foo" m with
+      | Some foo ->
+          insist (global_initializer foo =
+                  Some (const_int (i32_type context) 42))
+      | None ->
+          failwith "global")
   end;
 
   begin
     let buf = MemoryBuffer.of_string "@foo = global garble" in
-    try
-      ignore (parse_ir context buf);
+    let parsed = protect ~finally:(fun () -> MemoryBuffer.dispose buf)
+                   (fun () ->
+                     try
+                       let m = parse_ir_bitcode_or_assembly context buf in
+                       dispose_module m;
+                       true
+                     with Llvm_irreader.Error _ ->
+                       false)
+    in
+    if parsed then
       failwith "parsed"
-    with Llvm_irreader.Error _ ->
-      ()
   end
 
 



More information about the llvm-commits mailing list