[Mlir-commits] [mlir] [mlir][acc] Add data directive codegen to ACCToLLVM (PR #223043)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Fri Sep 11 13:03:28 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-openacc
Author: Razvan Lupusoru (razvanlupusoru)
<details>
<summary>Changes</summary>
Extends the acc to LLVM conversion with codegen for the OpenACC data constructs - data, enter data, exit data and update. Each construct becomes calls to the libacctarget data entry points that establish the mappings its clauses ask for and tear them down again, on the queue its async clause selects and after the wait its wait clause asks for. As with the executable directives, these runtime APIs are not yet finalized; the draft proposal can be found at https://github.com/llvm/llvm-project/pull/197894.
A mapping is handed to the runtime as parallel argument arrays holding one entry per mapped object, together with a descriptor for the objects whose extent or layout cannot be stated as a size. The descriptor layouts are declared next to the entry points they are passed to, as they are equally part of the runtime interface.
Clause handling the data constructs share with the executable directives, such as async, wait and if, now lives in the common conversion utilities.
---
Patch is 156.54 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/223043.diff
23 Files Affected:
- (added) mlir/include/mlir/Conversion/OpenACCToLLVM/ACCDataRuntime.h (+121)
- (modified) mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVM.h (+35-2)
- (modified) mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h (+199-5)
- (added) mlir/include/mlir/Dialect/OpenACC/OpenACCRuntimeDescriptors.def (+81)
- (modified) mlir/include/mlir/Dialect/OpenACC/OpenACCRuntimeFunctions.def (+45)
- (modified) mlir/include/mlir/Dialect/OpenACC/OpenACCRuntimeUtils.h (+76-5)
- (modified) mlir/include/mlir/Dialect/OpenACC/OpenACCUtilsCG.h (+12)
- (added) mlir/lib/Conversion/OpenACCToLLVM/ACCDataDirectivePatterns.cpp (+278)
- (added) mlir/lib/Conversion/OpenACCToLLVM/ACCDataRuntime.cpp (+477)
- (modified) mlir/lib/Conversion/OpenACCToLLVM/ACCExecutableDirectivePatterns.cpp (+42-117)
- (modified) mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVM.cpp (+9-4)
- (modified) mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVMUtils.cpp (+179-31)
- (modified) mlir/lib/Conversion/OpenACCToLLVM/CMakeLists.txt (+3)
- (modified) mlir/lib/Dialect/OpenACC/Utils/OpenACCRuntimeUtils.cpp (+106-3)
- (modified) mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsCG.cpp (+39-6)
- (added) mlir/test/Conversion/OpenACCToLLVM/data-diagnostics.mlir (+13)
- (added) mlir/test/Conversion/OpenACCToLLVM/data-runtime.mlir (+522)
- (added) mlir/test/Conversion/OpenACCToLLVM/data.mlir (+343)
- (modified) mlir/test/Conversion/OpenACCToLLVM/init-shutdown-set.mlir (+13-13)
- (modified) mlir/test/Conversion/OpenACCToLLVM/runtime-declaration-mismatch.mlir (+21)
- (modified) mlir/test/Conversion/OpenACCToLLVM/wait.mlir (+8-8)
- (modified) mlir/unittests/Dialect/OpenACC/CMakeLists.txt (+1)
- (added) mlir/unittests/Dialect/OpenACC/OpenACCRuntimeUtilsTest.cpp (+119)
``````````diff
diff --git a/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCDataRuntime.h b/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCDataRuntime.h
new file mode 100644
index 0000000000000..7ce82b5339866
--- /dev/null
+++ b/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCDataRuntime.h
@@ -0,0 +1,121 @@
+//===- ACCDataRuntime.h - OpenACC data runtime arguments --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Arguments and descriptors the OpenACC data runtime entry points are called
+// with. A conversion that lowers its own construct to those entry points emits
+// them with these, rather than restating the argument layout.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_CONVERSION_OPENACCTOLLVM_ACCDATARUNTIME_H
+#define MLIR_CONVERSION_OPENACCTOLLVM_ACCDATARUNTIME_H
+
+#include "mlir/Dialect/OpenACC/OpenACCRuntimeUtils.h"
+#include "mlir/IR/Value.h"
+#include "llvm/ADT/SmallVector.h"
+
+#include <cstdint>
+
+namespace mlir {
+class MemRefType;
+class Operation;
+class ConversionPatternRewriter;
+class RewriterBase;
+class Region;
+class SymbolTable;
+
+namespace acc {
+class OpenACCSupport;
+} // namespace acc
+
+/// The arguments every mapping entry point of the OpenACC runtime takes, in the
+/// order they are passed. An entry point that maps `argNum` objects reads one
+/// element from each of the arrays below per object; an entry point may also
+/// take further arguments of its own, which are not part of this set.
+struct ACCDataRuntimeArgs {
+ /// Source position and enclosing function name of the directive.
+ Value ident;
+ /// Reserved for per-call runtime flags; no flag is defined yet.
+ Value flags;
+ /// Device type the directive applies to, in the runtime encoding.
+ Value deviceType;
+ /// Number of mapped objects, that is, the length of each array below.
+ Value argNum;
+ /// Array of pointer slots to attach a mapped object to, null when the object
+ /// is not attached to anything.
+ Value argBasePtrs;
+ /// Array of addresses of the mapped objects themselves.
+ Value argPtrs;
+ /// Array of object sizes in bytes; zero where the descriptor or the bounds
+ /// state the extent instead.
+ Value argSizes;
+ /// Array of map-type flags, in the runtime encoding of `acc::MapFlags`.
+ Value argTypes;
+ /// Array of variable names for runtime diagnostics, null where unknown.
+ Value argNames;
+ /// Array of user-defined mappers. OpenACC has none, so this is always null.
+ Value argMappers;
+ /// Array of descriptors stating layout and bounds, null for whole objects of
+ /// a known size.
+ Value argDescs;
+
+ /// Returns the fields above in the order the entry points take them, so that
+ /// a caller only appends the arguments specific to the one it calls.
+ SmallVector<Value> getCallArgs() const;
+};
+
+/// Identifies how data runtime arguments will be consumed.
+enum class ACCDataCallKind {
+ DataEnter,
+ DataExit,
+};
+
+/// Materialize \p values as a stack-allocated LLVM array.
+Value createACCDataArray(Location loc, Type elementType, ArrayRef<Value> values,
+ RewriterBase &rewriter);
+
+/// Wrap \p baseDescriptor in the OpenACC overlay that carries \p bounds.
+/// Field zero of \p baseDescriptor is set to \p version with the OpenACC
+/// descriptor bit added.
+Value createACCDataDescriptor(Location loc, Value baseDescriptor,
+ Type baseDescriptorType, uint32_t version,
+ ValueRange bounds, Value elementSize,
+ ConversionPatternRewriter &rewriter);
+
+/// Build the runtime argument descriptor wrapping an already converted memref.
+/// The memref descriptor itself is the one the memref-to-LLVM conversion
+/// produced; this points the runtime at it. When \p bounds is non-empty, the
+/// wrapper is nested in the OpenACC overlay that carries those bounds instead
+/// of being stored on its own.
+Value createACCMemRefDescriptorWrapperArg(Location loc, MemRefType memrefType,
+ Value convertedMemref,
+ ValueRange bounds, Value elementSize,
+ ConversionPatternRewriter &rewriter);
+
+/// Build the runtime argument descriptor for \p mapOp, or a null pointer when
+/// the mapping needs no descriptor.
+Value createACCArgumentDescriptor(Operation *mapOp, Value convertedOperand,
+ acc::OpenACCSupport &accSupport,
+ ConversionPatternRewriter &rewriter);
+
+/// Emit the OpenACC data runtime arguments for data-clause operands. The
+/// operands may be `acc.map_info` or the data-clause operations it replaces.
+/// The emitted arrays name globals after the mapped objects and the position
+/// of the directive; they are created in \p globalSymbolRegion, with \p
+/// symbolTable as in acc::getOrCreateGlobalString.
+LogicalResult emitACCDataRuntimeArgs(
+ Location loc, ValueRange mappingOperands, ValueRange convertedOperands,
+ ConversionPatternRewriter &rewriter, Region &globalSymbolRegion,
+ acc::OpenACCSupport &accSupport, const acc::ACCRuntimeCallConfig &config,
+ ACCDataRuntimeArgs &runtimeArgs,
+ ACCDataCallKind callKind = ACCDataCallKind::DataEnter,
+ SymbolTable *symbolTable = nullptr);
+
+} // namespace mlir
+
+#endif // MLIR_CONVERSION_OPENACCTOLLVM_ACCDATARUNTIME_H
diff --git a/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVM.h b/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVM.h
index 6e69aca0f1c71..039a513844936 100644
--- a/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVM.h
+++ b/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVM.h
@@ -10,6 +10,8 @@
#define MLIR_CONVERSION_OPENACCTOLLVM_ACCTOLLVM_H
#include "mlir/Dialect/OpenACC/OpenACCRuntimeUtils.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/Value.h"
#include <functional>
#include <memory>
@@ -20,7 +22,8 @@ class LLVMTypeConverter;
class Operation;
class Pass;
class RewritePatternSet;
-class Value;
+class Region;
+class SymbolTable;
namespace acc {
class OpenACCSupport;
@@ -35,9 +38,12 @@ void configureACCExecutableDirectiveConversionLegality(
ConversionTarget &target);
/// Populate patterns that lower OpenACC executable directives (init, shutdown,
-/// wait, set) to LLVM runtime calls.
+/// wait, set) to LLVM runtime calls. The runtime declarations and globals the
+/// patterns add are created in \p globalSymbolRegion and registered in
+/// \p symbolTable.
void populateACCExecutableDirectivePatterns(
LLVMTypeConverter &converter, RewritePatternSet &patterns,
+ Region &globalSymbolRegion, SymbolTable &symbolTable,
const acc::ACCRuntimeCallConfig &config = {});
/// Returns the address operand of a dialect-specific load operation. Used when
@@ -55,6 +61,33 @@ void populateACCAtomicPatterns(
acc::OpenACCSupport &accSupport,
ACCAtomicLoadAddressCallback getLoadAddress = {});
+/// Configure conversion legality for OpenACC data directives.
+void configureACCDataDirectiveConversionLegality(ConversionTarget &target);
+
+/// Populate patterns that lower OpenACC data directives (`acc.data`,
+/// `enter_data`, `exit_data`, `update`) to `__tgt_acc_data_*` runtime calls.
+/// Clauses that can be repeated per device type are taken from the ones that
+/// apply to \p clauseDeviceType. The runtime declarations and globals the
+/// patterns add are created in \p globalSymbolRegion and registered in
+/// \p symbolTable.
+void populateACCDataDirectivePatterns(
+ LLVMTypeConverter &converter, RewritePatternSet &patterns,
+ acc::OpenACCSupport &accSupport, Region &globalSymbolRegion,
+ SymbolTable &symbolTable, const acc::ACCRuntimeCallConfig &config = {},
+ acc::DeviceType clauseDeviceType = acc::DeviceType::None);
+
+/// Populate the patterns that remove OpenACC data clause operations once the
+/// constructs holding them have turned their mappings into runtime calls. A
+/// data entry operation is replaced by the address of the object it named, and
+/// a data exit or bounds operation is erased. Clause operations whose result
+/// is not that address stay with the construct that holds them.
+///
+/// A conversion has to populate these only once every construct holding such a
+/// clause operation is lowered in the same conversion, as the mappings would
+/// otherwise be lost.
+void populateACCDataClauseOpPatterns(LLVMTypeConverter &converter,
+ RewritePatternSet &patterns);
+
} // namespace mlir
#endif // MLIR_CONVERSION_OPENACCTOLLVM_ACCTOLLVM_H
diff --git a/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h b/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h
index 7e16cf17c4489..73d4ca156b9e0 100644
--- a/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h
+++ b/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h
@@ -9,14 +9,22 @@
#ifndef MLIR_CONVERSION_OPENACCTOLLVM_ACCTOLLVMUTILS_H
#define MLIR_CONVERSION_OPENACCTOLLVM_ACCTOLLVMUTILS_H
+#include "mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h"
#include "mlir/Dialect/OpenACC/OpenACCRuntimeUtils.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/Location.h"
+#include "mlir/IR/PatternMatch.h"
+#include "mlir/IR/Region.h"
+#include "mlir/Transforms/DialectConversion.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/STLForwardCompat.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/StringRef.h"
#include <optional>
#include <string>
+#include <utility>
namespace mlir {
namespace acc {
@@ -28,7 +36,8 @@ Location unfuseLoc(Location loc);
std::optional<FileLineColLoc> getFileLineColLoc(Location loc,
bool errorOnInvalidLocation);
-/// Returns the enclosing function symbol name for \p op.
+/// Returns the symbol name of the function \p op belongs to, or of \p op itself
+/// when it is a function.
StringRef getParentFunctionName(Operation *op);
/// Returns the enclosing function symbol name for \p value's defining op.
@@ -37,14 +46,199 @@ StringRef getParentFunctionName(Value value);
/// Returns the first non-empty enclosing function name from \p values.
StringRef getParentFunctionName(ValueRange values);
-/// Creates or reuses a module-internal null-terminated string global.
+/// Returns the name to give a global that the conversion creates to hold
+/// \p detail of \p kind, such as the name of a variable or a source position.
+/// The dots make a name no identifier of the program can carry, so these
+/// globals are reachable by name without walking the symbols of the module.
+/// \p detail keeps letters, digits, `_`, `$` and `.` and folds every other
+/// character to an underscore. Distinct details can therefore collide on the
+/// same name; getOrCreateGlobalString is what then keeps the globals apart.
+std::string getInternalGlobalName(StringRef kind, StringRef detail);
+
+/// Creates or reuses a null-terminated string global in \p globalSymbolRegion.
+/// With \p symbolTable, a global already going by \p name is reused when it
+/// holds \p value, and a name that two different values arrive under gets a
+/// suffix to tell the globals apart. Without it the global is created under
+/// \p name as given, which only a caller whose names are unique by
+/// construction can ask for.
Value getOrCreateGlobalString(Location loc, OpBuilder &builder, StringRef name,
- StringRef value, ModuleOp module);
+ StringRef value, Region &globalSymbolRegion,
+ SymbolTable *symbolTable = nullptr);
/// Returns a pointer to a constant global holding an ident_t for OpenACC
-/// runtime calls.
+/// runtime calls. \p globalSymbolRegion and \p symbolTable are as in
+/// getOrCreateGlobalString; the ident and the source string it points to are
+/// named after the position they describe, so leaving out the table creates a
+/// set of them per call.
Value createIdent(Location loc, StringRef functionName, OpBuilder &builder,
- ModuleOp module, const ACCRuntimeCallConfig &config);
+ Region &globalSymbolRegion,
+ const ACCRuntimeCallConfig &config,
+ SymbolTable *symbolTable = nullptr);
+
+/// Sign-extends or truncates \p value to the i64 the runtime entry points take
+/// for values like queue numbers.
+Value castToI64(Location loc, Value value, OpBuilder &builder);
+
+/// Returns the queue an `async` clause selects: the value of the clause when it
+/// has one, the queue standing for an `async` clause without a value when
+/// \p asyncOnly is set, and the synchronous queue when there is no clause at
+/// all. \p asyncOperand must already be converted to the LLVM dialect.
+Value getAsyncQueue(Location loc, Value asyncOperand, bool asyncOnly,
+ OpBuilder &builder, const ACCRuntimeCallConfig &config);
+
+/// Emits the runtime call that waits for \p waitOperands on \p asyncQueue,
+/// which is what a `wait` clause or an `acc.wait` directive asks for. An empty
+/// \p waitOperands waits for every queue, as a `wait` clause without values
+/// does. The values must already be converted to the LLVM dialect.
+LogicalResult emitWaitCall(Location loc, ValueRange waitOperands,
+ Value asyncQueue, OpBuilder &builder,
+ Region &globalSymbolRegion, SymbolTable &symbolTable,
+ const ACCRuntimeCallConfig &config);
+
+/// Runs \p emitFn guarded by a branch on \p ifCond, or unguarded when there is
+/// no condition. Leaves the insertion point after the guarded code, so that a
+/// caller can keep emitting into the same block either way.
+LogicalResult emitGuardedByIfCond(Location loc, Value ifCond,
+ RewriterBase &rewriter,
+ function_ref<LogicalResult()> emitFn);
+
+/// The clauses of a construct can be given once per device type. Of the values
+/// that reach a given device type, the ones naming it are the most specific,
+/// then the ones naming every device type, then the ones given before any
+/// device_type clause.
+SmallVector<DeviceType, 3> getDeviceTypesByPrecedence(DeviceType deviceType);
+
+namespace detail {
+/// The constructs carrying a `device_type` clause hold their async and wait
+/// clauses per device type. The others, such as `acc.enter_data` or
+/// `acc.kernel_environment`, hold a single value for each clause, spelling the
+/// value-less form either `asyncOnly`/`waitOnly` or `async`/`wait`.
+template <typename OpTy>
+using has_device_type_clauses_t =
+ decltype(std::declval<OpTy>().hasAsyncOnly(DeviceType::None));
+template <typename OpTy>
+using has_async_only_t = decltype(std::declval<OpTy>().getAsyncOnly());
+template <typename OpTy>
+using has_wait_only_t = decltype(std::declval<OpTy>().getWaitOnly());
+} // namespace detail
+
+/// Returns the value the `async` clause of \p op names for \p deviceType, and
+/// sets \p asyncOnly when the clause names no queue. The value is the one the
+/// operation holds, so a caller in a conversion has to remap it.
+template <typename OpTy>
+Value getAsyncClauseValue(OpTy op, DeviceType deviceType, bool &asyncOnly) {
+ asyncOnly = false;
+ if constexpr (llvm::is_detected<detail::has_device_type_clauses_t,
+ OpTy>::value) {
+ for (DeviceType candidate : getDeviceTypesByPrecedence(deviceType)) {
+ if (op.hasAsyncOnly(candidate)) {
+ asyncOnly = true;
+ return {};
+ }
+ if (Value asyncValue = op.getAsyncValue(candidate))
+ return asyncValue;
+ }
+ return {};
+ } else if constexpr (llvm::is_detected<detail::has_async_only_t,
+ OpTy>::value) {
+ asyncOnly = op.getAsyncOnly();
+ return op.getAsyncOperand();
+ } else {
+ asyncOnly = op.getAsync();
+ return op.getAsyncOperand();
+ }
+}
+
+/// Appends to \p waitValues the queues the `wait` clause of \p op names for
+/// \p deviceType, and returns the device type the clause naming them is given
+/// for - a clause naming no queue waits for every one of them. Returns
+/// std::nullopt when \p op gives no such clause for \p deviceType. The values
+/// are the ones the operation holds, so a caller in a conversion has to remap
+/// them.
+template <typename OpTy>
+std::optional<DeviceType>
+getWaitClauseValues(OpTy op, DeviceType deviceType,
+ SmallVectorImpl<Value> &waitValues) {
+ if constexpr (llvm::is_detected<detail::has_device_type_clauses_t,
+ OpTy>::value) {
+ for (DeviceType candidate : getDeviceTypesByPrecedence(deviceType)) {
+ if (op.hasWaitOnly(candidate))
+ return candidate;
+ auto values = op.getWaitValues(candidate);
+ if (!values.empty()) {
+ llvm::append_range(waitValues, values);
+ return candidate;
+ }
+ }
+ return std::nullopt;
+ } else {
+ llvm::append_range(waitValues, op.getWaitOperands());
+ bool waitsForEveryQueue;
+ if constexpr (llvm::is_detected<detail::has_wait_only_t, OpTy>::value)
+ waitsForEveryQueue = op.getWaitOnly();
+ else
+ waitsForEveryQueue = op.getWait();
+ if (!waitsForEveryQueue && waitValues.empty())
+ return std::nullopt;
+ // The operation holds a single wait clause, which no device_type clause
+ // narrows to a device type.
+ return DeviceType::None;
+ }
+}
+
+/// Returns whether the `wait` clause \p op gives for \p deviceType carries a
+/// devnum modifier, which selects the device the queues belong to. Only the
+/// clause given for that device type is asked, so a caller passes the device
+/// type getWaitClauseValues took the queues from.
+template <typename OpTy>
+bool hasWaitDevnum(OpTy op, DeviceType deviceType) {
+ if constexpr (llvm::is_detected<detail::has_device_type_clauses_t,
+ OpTy>::value) {
+ return static_cast<bool>(op.getWaitDevnum(deviceType));
+ } else {
+ return static_cast<bool>(op.getWaitDevnum());
+ }
+}
+
+/// Returns the queue that the runtime calls of \p op run on, from the `async`
+/// clause it gives for \p deviceType.
+template <typename OpTy>
+Value getAsyncQueue(OpTy op, DeviceType deviceType,
+ ConversionPatternRewriter &rewriter,
+ const ACCRuntimeCallConfig &config) {
+ bool asyncOnly = false;
+ Value asyncValue = getAsyncClauseValue(op, deviceType, asyncOnly);
+ if (asyncValue)
+ asyncValue = rewriter.getRemappedValue(asyncValue);
+ return getAsyncQueue(op.getLoc(), asyncValue, asyncOnly, rewriter, config);
+}
+
+/// Emits the wait that a `wait` clause on \p op asks for before the runtime
+/// calls of the construct, waiting on \p asyncQueue for the queues the clause
+/// names, or for every queue when it names none. Nothing is emitted when there
+/// is no such clause for \p deviceType. A devnum modifier, which selects the
+/// device the queues belong to, is reported through \p accSupport as not yet
+/// implemented.
+template <typename OpTy>
+LogicalResult
+emitWaitClause(OpTy op, DeviceType deviceType, Value asyncQueue,
+ ConversionPatternRewriter &rewriter, OpenACCSupport &accSupport,
+ Region &globalSymbolRegion, SymbolTable &symbolTable,
+ const ACCRuntimeCallConfig &config) {
+ SmallVector<Value> waitValues;
+ std::optional<DeviceType> clauseDeviceType =
+ getWaitClauseValues(op, deviceType, waitValues);
+ if (!clauseDeviceType)
+ return success();
+ if (hasWaitDevnum(op, *clauseDeviceType)) {
+ (void)accSupport.emitNYI(op.getLoc(), "wait clause with a devnum modifier");
+ return failure();
+ }
+ for (Value &waitValue : waitValues)
+ waitValue = rewriter.getRemappedValue(waitValue);
+ return emitWaitCall(op.getLoc(), waitValues, asyncQueue, rewriter,
+ globalSymbolRegion, symbolTable, config);
+}
} // namespace acc
} // namespace mlir
diff --git a/mlir...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/223043
More information about the Mlir-commits
mailing list