diff --git a/mlir/include/mlir/Conversion/OpenACCToLLVM/ConvertOpenACCToLLVM.h b/mlir/include/mlir/Conversion/OpenACCToLLVM/ConvertOpenACCToLLVM.h new file mode 100644 --- /dev/null +++ b/mlir/include/mlir/Conversion/OpenACCToLLVM/ConvertOpenACCToLLVM.h @@ -0,0 +1,75 @@ +//===- ConvertOpenACCToLLVM.h - OpenACC conversion pass entrypoint --------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +#ifndef MLIR_CONVERSION_OPENACCTOLLVM_CONVERTOPENACCTOLLVM_H +#define MLIR_CONVERSION_OPENACCTOLLVM_CONVERTOPENACCTOLLVM_H + +#include "mlir/Conversion/StandardToLLVM/ConvertStandardToLLVM.h" +#include + +namespace mlir { +class LLVMTypeConverter; +class ModuleOp; +template +class OperationPass; +class RewritePatternSet; + +static constexpr unsigned kPtrBasePosInDataDescriptor = 0; +static constexpr unsigned kPtrPosInDataDescriptor = 1; +static constexpr unsigned kSizePosInDataDescriptor = 2; + +/// Helper class to produce LLVM dialect operations inserting +/// elements to a Data descriptor. Wraps a Value pointing to the descriptor. +/// The Value may be null, in which case none of the operations are valid. +/// +/// The data descriptor holds information needed to perform data operations +/// and movments with the runtime. +/// `BasePointer`: base of the pointer being mapped. +/// `Pointer`: actual pointer of the data being mapped. +/// `Size`: size of the data being mapped. +/// +/// Example: +/// +/// ```c +/// struct S { +/// int x; +/// int y; +/// }; +/// ``` +/// +/// Mapping `s.y` will result if the following information in the data +/// descriptor: +/// - `BasePointer`: address of `s` +/// - `Pointer`: address of `s.y` +/// - `Size`: size of `s.y` +/// +/// For a scalar variable BasePointer and Pointer will be the same. +class DataDescriptor : public StructBuilder { +public: + /// Construct a helper for the given descriptor value. + explicit DataDescriptor(Value descriptor); + /// Builds IR creating an `undef` value of the descriptor type. + static DataDescriptor undef(OpBuilder &builder, Location loc, Type basePtrTy, + Type ptrTy); + + static bool isValid(Value descriptor); + + void setPointer(OpBuilder &builder, Location loc, Value ptr); + void setBasePointer(OpBuilder &builder, Location loc, Value basePtr); + void setSize(OpBuilder &builder, Location loc, Value size); +}; + +/// Collect the patterns to convert from the OpenACC dialect LLVMIR dialect. +void populateOpenACCToLLVMConversionPatterns(LLVMTypeConverter &converter, + RewritePatternSet &patterns); + +/// Create a pass to convert the OpenACC dialect into the LLVMIR dialect. +std::unique_ptr> createConvertOpenACCToLLVMPass(); + +} // namespace mlir + +#endif // MLIR_CONVERSION_OPENACCTOLLVM_CONVERTOPENACCTOLLVM_H diff --git a/mlir/include/mlir/Conversion/Passes.h b/mlir/include/mlir/Conversion/Passes.h --- a/mlir/include/mlir/Conversion/Passes.h +++ b/mlir/include/mlir/Conversion/Passes.h @@ -22,6 +22,7 @@ #include "mlir/Conversion/LinalgToSPIRV/LinalgToSPIRVPass.h" #include "mlir/Conversion/LinalgToStandard/LinalgToStandard.h" #include "mlir/Conversion/MathToLibm/MathToLibm.h" +#include "mlir/Conversion/OpenACCToLLVM/ConvertOpenACCToLLVM.h" #include "mlir/Conversion/OpenMPToLLVM/ConvertOpenMPToLLVM.h" #include "mlir/Conversion/PDLToPDLInterp/PDLToPDLInterp.h" #include "mlir/Conversion/SCFToGPU/SCFToGPUPass.h" diff --git a/mlir/include/mlir/Conversion/Passes.td b/mlir/include/mlir/Conversion/Passes.td --- a/mlir/include/mlir/Conversion/Passes.td +++ b/mlir/include/mlir/Conversion/Passes.td @@ -255,6 +255,16 @@ let dependentDialects = ["StandardOpsDialect", "vector::VectorDialect"]; } +//===----------------------------------------------------------------------===// +// OpenACCToLLVM +//===----------------------------------------------------------------------===// + +def ConvertOpenACCToLLVM : Pass<"convert-openacc-to-llvm", "ModuleOp"> { + let summary = "Convert the OpenACC ops to LLVM dialect"; + let constructor = "mlir::createConvertOpenACCToLLVMPass()"; + let dependentDialects = ["LLVM::LLVMDialect"]; +} + //===----------------------------------------------------------------------===// // OpenMPToLLVM //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td --- a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td +++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td @@ -249,6 +249,14 @@ Variadic:$createZeroOperands, Variadic:$attachOperands); + let extraClassDeclaration = [{ + /// The number of data operands. + unsigned getNumDataOperands(); + + /// The i-th data operand passed. + Value getDataOperand(unsigned i); + }]; + let assemblyFormat = [{ ( `if` `(` $ifCond^ `)` )? ( `async` `(` $asyncOperand^ `:` type($asyncOperand) `)` )? @@ -291,6 +299,14 @@ Variadic:$detachOperands, UnitAttr:$finalize); + let extraClassDeclaration = [{ + /// The number of data operands. + unsigned getNumDataOperands(); + + /// The i-th data operand passed. + Value getDataOperand(unsigned i); + }]; + let assemblyFormat = [{ ( `if` `(` $ifCond^ `)` )? ( `async` `(` $asyncOperand^ `:` type($asyncOperand) `)` )? @@ -480,6 +496,14 @@ Variadic:$deviceOperands, UnitAttr:$ifPresent); + let extraClassDeclaration = [{ + /// The number of data operands. + unsigned getNumDataOperands(); + + /// The i-th data operand passed. + Value getDataOperand(unsigned i); + }]; + let assemblyFormat = [{ ( `if` `(` $ifCond^ `)` )? ( `async` `(` $asyncOperand^ `:` type($asyncOperand) `)` )? diff --git a/mlir/lib/Conversion/CMakeLists.txt b/mlir/lib/Conversion/CMakeLists.txt --- a/mlir/lib/Conversion/CMakeLists.txt +++ b/mlir/lib/Conversion/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory(LinalgToSPIRV) add_subdirectory(LinalgToStandard) add_subdirectory(MathToLibm) +add_subdirectory(OpenACCToLLVM) add_subdirectory(OpenMPToLLVM) add_subdirectory(PDLToPDLInterp) add_subdirectory(SCFToGPU) diff --git a/mlir/lib/Conversion/OpenACCToLLVM/CMakeLists.txt b/mlir/lib/Conversion/OpenACCToLLVM/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/mlir/lib/Conversion/OpenACCToLLVM/CMakeLists.txt @@ -0,0 +1,16 @@ +add_mlir_conversion_library(MLIROpenACCToLLVM + OpenACCToLLVM.cpp + + ADDITIONAL_HEADER_DIRS + ${MLIR_MAIN_INCLUDE_DIR}/mlir/Conversion/OpenACCToLLVM + + DEPENDS + MLIRConversionPassIncGen + + LINK_LIBS PUBLIC + MLIRIR + MLIRLLVMIR + MLIROpenACC + MLIRStandardToLLVM + MLIRTransforms + ) diff --git a/mlir/lib/Conversion/OpenACCToLLVM/OpenACCToLLVM.cpp b/mlir/lib/Conversion/OpenACCToLLVM/OpenACCToLLVM.cpp new file mode 100644 --- /dev/null +++ b/mlir/lib/Conversion/OpenACCToLLVM/OpenACCToLLVM.cpp @@ -0,0 +1,198 @@ +//===- OpenACCToLLVM.cpp - Prepare OpenACC data for LLVM translation ------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "../PassDetail.h" +#include "mlir/Conversion/OpenACCToLLVM/ConvertOpenACCToLLVM.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/Dialect/OpenACC/OpenACC.h" + +using namespace mlir; + +//===----------------------------------------------------------------------===// +// DataDescriptor implementation +//===----------------------------------------------------------------------===// + +constexpr StringRef getStructName() { return "openacc_data"; } + +/// Construct a helper for the given descriptor value. +DataDescriptor::DataDescriptor(Value descriptor) : StructBuilder(descriptor) { + assert(value != nullptr && "value cannot be null"); +} + +/// Builds IR creating an `undef` value of the data descriptor. +DataDescriptor DataDescriptor::undef(OpBuilder &builder, Location loc, + Type basePtrTy, Type ptrTy) { + Type descriptorType = LLVM::LLVMStructType::getNewIdentified( + builder.getContext(), getStructName(), + {basePtrTy, ptrTy, builder.getI64Type()}); + Value descriptor = builder.create(loc, descriptorType); + return DataDescriptor(descriptor); +} + +/// Check whether the type is a valid data descriptor. +bool DataDescriptor::isValid(Value descriptor) { + if (auto type = descriptor.getType().dyn_cast()) { + if (type.isIdentified() && type.getName().startswith(getStructName()) && + type.getBody().size() == 3 && + (type.getBody()[kPtrBasePosInDataDescriptor] + .isa() || + type.getBody()[kPtrBasePosInDataDescriptor] + .isa()) && + type.getBody()[kPtrPosInDataDescriptor].isa() && + type.getBody()[kSizePosInDataDescriptor].isInteger(64)) + return true; + } + return false; +} + +/// Builds IR inserting the base pointer value into the descriptor. +void DataDescriptor::setBasePointer(OpBuilder &builder, Location loc, + Value basePtr) { + setPtr(builder, loc, kPtrBasePosInDataDescriptor, basePtr); +} + +/// Builds IR inserting the pointer value into the descriptor. +void DataDescriptor::setPointer(OpBuilder &builder, Location loc, Value ptr) { + setPtr(builder, loc, kPtrPosInDataDescriptor, ptr); +} + +/// Builds IR inserting the size value into the descriptor. +void DataDescriptor::setSize(OpBuilder &builder, Location loc, Value size) { + setPtr(builder, loc, kSizePosInDataDescriptor, size); +} + +//===----------------------------------------------------------------------===// +// Conversion patterns +//===----------------------------------------------------------------------===// + +template +class LegalizeDataOpForLLVMTranslation : public ConvertOpToLLVMPattern { + using ConvertOpToLLVMPattern::ConvertOpToLLVMPattern; + + LogicalResult + matchAndRewrite(Op op, ArrayRef operands, + ConversionPatternRewriter &builder) const override { + Location loc = op.getLoc(); + TypeConverter *converter = ConvertToLLVMPattern::getTypeConverter(); + + unsigned numDataOperand = op.getNumDataOperands(); + + // Keep the non data operands without modification. + auto nonDataOperands = + operands.take_front(operands.size() - numDataOperand); + SmallVector convertedOperands; + convertedOperands.append(nonDataOperands.begin(), nonDataOperands.end()); + + // Go over the data operand and legalize them for translation. + for (unsigned idx = 0; idx < numDataOperand; ++idx) { + Value originalDataOperand = op.getDataOperand(idx); + + // Traverse operands that were converted to MemRefDescriptors. + if (auto memRefType = + originalDataOperand.getType().dyn_cast()) { + Type structType = converter->convertType(memRefType); + Value memRefDescriptor = builder + .create( + loc, structType, originalDataOperand) + .getResult(); + + // Calculate the size of the memref and get the pointer to the allocated + // buffer. + SmallVector sizes; + SmallVector strides; + Value sizeBytes; + ConvertToLLVMPattern::getMemRefDescriptorSizes( + loc, memRefType, {}, builder, sizes, strides, sizeBytes); + MemRefDescriptor descriptor(memRefDescriptor); + Value dataPtr = descriptor.alignedPtr(builder, loc); + auto ptrType = descriptor.getElementPtrType(); + + auto descr = DataDescriptor::undef(builder, loc, structType, ptrType); + descr.setBasePointer(builder, loc, memRefDescriptor); + descr.setPointer(builder, loc, dataPtr); + descr.setSize(builder, loc, sizeBytes); + convertedOperands.push_back(descr); + } else if (originalDataOperand.getType().isa()) { + convertedOperands.push_back(originalDataOperand); + } else { + // Type not supported. + return builder.notifyMatchFailure(op, "unsupported type"); + } + } + + builder.replaceOpWithNewOp(op, TypeRange(), convertedOperands, + op.getOperation()->getAttrs()); + + return success(); + } +}; + +void mlir::populateOpenACCToLLVMConversionPatterns( + LLVMTypeConverter &converter, OwningRewritePatternList &patterns) { + patterns.add>(converter); + patterns.add>(converter); + patterns.add>(converter); +} + +namespace { +struct ConvertOpenACCToLLVMPass + : public ConvertOpenACCToLLVMBase { + void runOnOperation() override; +}; +} // namespace + +void ConvertOpenACCToLLVMPass::runOnOperation() { + auto op = getOperation(); + auto *context = op.getContext(); + + // Convert to OpenACC operations with LLVM IR dialect + RewritePatternSet patterns(context); + LLVMTypeConverter converter(context); + populateOpenACCToLLVMConversionPatterns(converter, patterns); + + ConversionTarget target(*context); + target.addLegalDialect(); + + auto allDataOperandsAreConverted = [](ValueRange operands) { + for (Value operand : operands) { + if (!DataDescriptor::isValid(operand) && + !operand.getType().isa()) + return false; + } + return true; + }; + + target.addDynamicallyLegalOp( + [allDataOperandsAreConverted](acc::EnterDataOp op) { + return allDataOperandsAreConverted(op.copyinOperands()) && + allDataOperandsAreConverted(op.createOperands()) && + allDataOperandsAreConverted(op.createZeroOperands()) && + allDataOperandsAreConverted(op.attachOperands()); + }); + + target.addDynamicallyLegalOp( + [allDataOperandsAreConverted](acc::ExitDataOp op) { + return allDataOperandsAreConverted(op.copyoutOperands()) && + allDataOperandsAreConverted(op.deleteOperands()) && + allDataOperandsAreConverted(op.detachOperands()); + }); + + target.addDynamicallyLegalOp( + [allDataOperandsAreConverted](acc::UpdateOp op) { + return allDataOperandsAreConverted(op.hostOperands()) && + allDataOperandsAreConverted(op.deviceOperands()); + }); + + if (failed(applyPartialConversion(op, target, std::move(patterns)))) + signalPassFailure(); +} + +std::unique_ptr> +mlir::createConvertOpenACCToLLVMPass() { + return std::make_unique(); +} diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp --- a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp +++ b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp @@ -682,8 +682,20 @@ return success(); } +unsigned ExitDataOp::getNumDataOperands() { + return copyoutOperands().size() + deleteOperands().size() + + detachOperands().size(); +} + +Value ExitDataOp::getDataOperand(unsigned i) { + unsigned numOptional = ifCond() ? 1 : 0; + numOptional += asyncOperand() ? 1 : 0; + numOptional += waitDevnum() ? 1 : 0; + return getOperand(waitOperands().size() + numOptional + i); +} + //===----------------------------------------------------------------------===// -// DataEnterOp +// EnterDataOp //===----------------------------------------------------------------------===// static LogicalResult verify(acc::EnterDataOp op) { @@ -712,6 +724,18 @@ return success(); } +unsigned EnterDataOp::getNumDataOperands() { + return copyinOperands().size() + createOperands().size() + + createZeroOperands().size() + attachOperands().size(); +} + +Value EnterDataOp::getDataOperand(unsigned i) { + unsigned numOptional = ifCond() ? 1 : 0; + numOptional += asyncOperand() ? 1 : 0; + numOptional += waitDevnum() ? 1 : 0; + return getOperand(waitOperands().size() + numOptional + i); +} + //===----------------------------------------------------------------------===// // InitOp //===----------------------------------------------------------------------===// @@ -766,6 +790,18 @@ return success(); } +unsigned UpdateOp::getNumDataOperands() { + return hostOperands().size() + deviceOperands().size(); +} + +Value UpdateOp::getDataOperand(unsigned i) { + unsigned numOptional = asyncOperand() ? 1 : 0; + numOptional += waitDevnum() ? 1 : 0; + numOptional += ifCond() ? 1 : 0; + return getOperand(waitOperands().size() + deviceTypeOperands().size() + + numOptional + i); +} + //===----------------------------------------------------------------------===// // WaitOp //===----------------------------------------------------------------------===// diff --git a/mlir/test/Conversion/OpenACCToLLVM/convert-standalone-data-to-llvmir.mlir b/mlir/test/Conversion/OpenACCToLLVM/convert-standalone-data-to-llvmir.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/Conversion/OpenACCToLLVM/convert-standalone-data-to-llvmir.mlir @@ -0,0 +1,110 @@ +// RUN: mlir-opt -convert-openacc-to-llvm -split-input-file %s | FileCheck %s + +func @testenterdataop(%a: memref<10xf32>, %b: memref<10xf32>) -> () { + acc.enter_data copyin(%b : memref<10xf32>) create(%a : memref<10xf32>) + return +} + +// CHECK: acc.enter_data copyin(%{{.*}} : !llvm.struct<"openacc_data", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) create(%{{.*}} : !llvm.struct<"openacc_data.1", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) + +// ----- + +func @testenterdataop(%a: !llvm.ptr, %b: memref<10xf32>) -> () { + acc.enter_data copyin(%b : memref<10xf32>) create(%a : !llvm.ptr) + return +} + +// CHECK: acc.enter_data copyin(%{{.*}} : !llvm.struct<"openacc_data", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) create(%{{.*}} : !llvm.ptr) + +// ----- + +func @testenterdataop(%a: memref<10xi64>, %b: memref<10xf32>) -> () { + acc.enter_data copyin(%b : memref<10xf32>) create_zero(%a : memref<10xi64>) attributes {async} + return +} + +// CHECK: acc.enter_data copyin(%{{.*}} : !llvm.struct<"openacc_data", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) create_zero(%{{.*}} : !llvm.struct<"openacc_data.1", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) attributes {async} + +// ----- + +func @testenterdataop(%a: memref<10xf32>, %b: memref<10xf32>) -> () { + %ifCond = constant true + acc.enter_data if(%ifCond) copyin(%b : memref<10xf32>) create(%a : memref<10xf32>) + return +} + +// CHECK: acc.enter_data if(%{{.*}}) copyin(%{{.*}} : !llvm.struct<"openacc_data", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) create(%{{.*}} : !llvm.struct<"openacc_data.1", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) + +// ----- + +func @testexitdataop(%a: memref<10xf32>, %b: memref<10xf32>) -> () { + acc.exit_data copyout(%b : memref<10xf32>) delete(%a : memref<10xf32>) + return +} + +// CHECK: acc.exit_data copyout(%{{.*}} : !llvm.struct<"openacc_data", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) delete(%{{.*}} : !llvm.struct<"openacc_data.1", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) + +// ----- + +func @testexitdataop(%a: !llvm.ptr, %b: memref<10xf32>) -> () { + acc.exit_data copyout(%b : memref<10xf32>) delete(%a : !llvm.ptr) + return +} + +// CHECK: acc.exit_data copyout(%{{.*}} : !llvm.struct<"openacc_data", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) delete(%{{.*}} : !llvm.ptr) + +// ----- + +func @testexitdataop(%a: memref<10xi64>, %b: memref<10xf32>) -> () { + acc.exit_data copyout(%b : memref<10xf32>) delete(%a : memref<10xi64>) attributes {async} + return +} + +// CHECK: acc.exit_data copyout(%{{.*}} : !llvm.struct<"openacc_data", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) delete(%{{.*}} : !llvm.struct<"openacc_data.1", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) attributes {async} + +// ----- + +func @testexitdataop(%a: memref<10xf32>, %b: memref<10xf32>) -> () { + %ifCond = constant true + acc.exit_data if(%ifCond) copyout(%b : memref<10xf32>) delete(%a : memref<10xf32>) + return +} + +// CHECK: acc.exit_data if(%{{.*}}) copyout(%{{.*}} : !llvm.struct<"openacc_data", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) delete(%{{.*}} : !llvm.struct<"openacc_data.1", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) + +// ----- + +func @testupdateop(%a: memref<10xf32>, %b: memref<10xf32>) -> () { + acc.update host(%b : memref<10xf32>) device(%a : memref<10xf32>) + return +} + +// CHECK: acc.update host(%{{.*}} : !llvm.struct<"openacc_data", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) device(%{{.*}} : !llvm.struct<"openacc_data.1", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) + +// ----- + +func @testupdateop(%a: !llvm.ptr, %b: memref<10xf32>) -> () { + acc.update host(%b : memref<10xf32>) device(%a : !llvm.ptr) + return +} + +// CHECK: acc.update host(%{{.*}} : !llvm.struct<"openacc_data", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) device(%{{.*}} : !llvm.ptr) + +// ----- + +func @testupdateop(%a: memref<10xi64>, %b: memref<10xf32>) -> () { + acc.update host(%b : memref<10xf32>) device(%a : memref<10xi64>) attributes {async} + return +} + +// CHECK: acc.update host(%{{.*}} : !llvm.struct<"openacc_data", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) device(%{{.*}} : !llvm.struct<"openacc_data.1", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) attributes {async} + +// ----- + +func @testupdateop(%a: memref<10xf32>, %b: memref<10xf32>) -> () { + %ifCond = constant true + acc.update if(%ifCond) host(%b : memref<10xf32>) device(%a : memref<10xf32>) + return +} + +// CHECK: acc.update if(%{{.*}}) host(%{{.*}} : !llvm.struct<"openacc_data", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>) device(%{{.*}} : !llvm.struct<"openacc_data.1", (struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>, ptr, i64)>)