blob: 6eb4547bf9491be00b689d1c9a03eac512262abe [file] [edit]
//===- ACCDataRuntime.cpp - Emit 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
//
//===----------------------------------------------------------------------===//
//
// Builds the mapping argument arrays used by OpenACC data runtime entry points.
//
//===----------------------------------------------------------------------===//
#include "mlir/Conversion/OpenACCToLLVM/ACCDataRuntime.h"
#include "mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/Dialect/OpenACC/OpenACCUtilsCG.h"
#include "mlir/Dialect/OpenACC/OpenACCUtilsType.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Debug.h"
#include <algorithm>
#define DEBUG_TYPE "acc-data-runtime"
using namespace mlir;
using namespace mlir::acc;
static Value remap(Value value, ConversionPatternRewriter &rewriter) {
if (!value)
return {};
if (Value converted = rewriter.getRemappedValue(value))
return converted;
return value;
}
static Value constantI64(Location loc, int64_t value,
ConversionPatternRewriter &rewriter) {
return LLVM::ConstantOp::create(rewriter, loc, rewriter.getI64Type(), value);
}
static Value getPointer(Location loc, Value value,
ConversionPatternRewriter &rewriter) {
Type ptrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
if (!value)
return LLVM::ZeroOp::create(rewriter, loc, ptrTy);
if (value.getType() == ptrTy)
return value;
if (isa<PointerLikeType>(value.getType()))
return castPointerLikeTypeIfNeeded(rewriter, loc, value, ptrTy);
Value one = LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(), 1);
Value storage =
LLVM::AllocaOp::create(rewriter, loc, ptrTy, value.getType(), one);
LLVM::StoreOp::create(rewriter, loc, value, storage);
return storage;
}
/// Returns the address of a slot holding \p value, so that the runtime reads
/// the value from memory rather than receiving it directly.
static Value getIndirectPointer(Location loc, Value value,
ConversionPatternRewriter &rewriter) {
Type ptrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
Value one = LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(), 1);
Value storage =
LLVM::AllocaOp::create(rewriter, loc, ptrTy, value.getType(), one);
LLVM::StoreOp::create(rewriter, loc, value, storage);
return storage;
}
Value mlir::createACCDataArray(Location loc, Type elementType,
ArrayRef<Value> values, RewriterBase &rewriter) {
Type ptrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
Type i32Ty = rewriter.getI32Type();
Value count = LLVM::ConstantOp::create(rewriter, loc, i32Ty, values.size());
Value array =
LLVM::AllocaOp::create(rewriter, loc, ptrTy, elementType, count);
for (auto [index, value] : llvm::enumerate(values)) {
Value indexValue = LLVM::ConstantOp::create(rewriter, loc, i32Ty, index);
Value element = LLVM::GEPOp::create(rewriter, loc, ptrTy, elementType,
array, ValueRange{indexValue});
LLVM::StoreOp::create(rewriter, loc, value, element);
}
return array;
}
/// The address of the mapped object, as the runtime expects to receive it.
/// Privatized storage only ever exists on the device, so there is no host
/// address to give. A device address names storage the runtime must not look
/// up, and an object mapped by value has no address at all, so both are handed
/// over in a slot the runtime reads them from.
static Value getMappedObjectPointer(Location loc, Value operand,
Value converted, MapFlags mapFlags,
ConversionPatternRewriter &rewriter) {
Type ptrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
if (isa<PrivateType>(operand.getType()))
return LLVM::ZeroOp::create(rewriter, loc, ptrTy);
if (converted && converted.getType() == ptrTy &&
bitEnumContainsAny(mapFlags, MapFlags::devptr))
return getIndirectPointer(loc, converted, rewriter);
return getPointer(loc, converted, rewriter);
}
static std::optional<MapFlags> computePackedMapFlags(Operation *mapOp) {
if (auto mapInfo = dyn_cast<MapInfoOp>(mapOp))
return mapInfo.getMapFlags();
if (isa<ACC_DATA_ENTRY_OPS>(mapOp))
return computeDataClauseMapFlags(mapOp, hasAttachPoint(mapOp));
return std::nullopt;
}
static Value getMapSize(Operation *mapOp, Value converted, MapFlags mapFlags,
OpenACCSupport &accSupport,
ConversionPatternRewriter &rewriter) {
Location loc = mapOp->getLoc();
ModuleOp module = mapOp->getParentOfType<ModuleOp>();
// An object mapped by value is passed as the argument itself, so its size is
// the size of what is passed and not of the object it was read from. The two
// differ when the argument holds one field of a decomposed aggregate.
if (bitEnumContainsAny(mapFlags, MapFlags::literal) && converted) {
if (auto sizeAndAlignment = acc::getTypeSizeAndAlignment(
converted.getType(), module, &accSupport))
return constantI64(loc, sizeAndAlignment->first.getFixedValue(),
rewriter);
}
if (Value size = acc::getMapSize(mapOp))
return castToI64(loc, remap(size, rewriter), rewriter);
Value var = acc::getVar(mapOp);
if (!var)
return constantI64(loc, 0, rewriter);
Type varType = acc::getVarType(mapOp);
if (!varType)
varType = var.getType();
// A memref is handed over with a descriptor stating its extents and element
// size, which is what states the size of such a mapping.
if (isa<MemRefType>(var.getType()) || isa<MemRefType>(varType))
return constantI64(loc, 0, rewriter);
// A data clause operation that was not turned into an `acc.map_info` has no
// size of its own, so it is sized the way that operation would have been.
// A size that is not statically known is left to the runtime, as an unsized
// mapping is.
int64_t size =
acc::computeMapInfoSizeBytes(var, varType, acc::getDataDescKind(mapOp),
acc::getBounds(mapOp), &accSupport);
return constantI64(loc, std::max<int64_t>(size, 0), rewriter);
}
static Value getElementSize(Operation *mapOp, OpenACCSupport &accSupport,
ConversionPatternRewriter &rewriter) {
Location loc = mapOp->getLoc();
ModuleOp module = mapOp->getParentOfType<ModuleOp>();
if (std::optional<int64_t> size = acc::getMapElementSize(mapOp))
return constantI64(loc, *size, rewriter);
Type type = acc::getVarType(mapOp);
if (auto shaped = dyn_cast<ShapedType>(type))
type = shaped.getElementType();
if (!type)
return constantI64(loc, 0, rewriter);
if (auto sizeAndAlignment =
acc::getTypeSizeAndAlignment(type, module, &accSupport))
return constantI64(loc, sizeAndAlignment->first.getFixedValue(), rewriter);
return constantI64(loc, 0, rewriter);
}
static Value getBoundValue(Value value, Location loc,
ConversionPatternRewriter &rewriter) {
if (!value)
return constantI64(loc, 0, rewriter);
return castToI64(loc, remap(value, rewriter), rewriter);
}
Value mlir::createACCDataDescriptor(Location loc, Value baseDescriptor,
Type baseDescriptorType, uint32_t version,
ValueRange bounds, Value elementSize,
ConversionPatternRewriter &rewriter) {
MLIRContext *context = rewriter.getContext();
Type i8Ty = rewriter.getI8Type();
Type i32Ty = rewriter.getI32Type();
Type i64Ty = rewriter.getI64Type();
Type ptrTy = LLVM::LLVMPointerType::get(context);
Value versionValue = LLVM::ConstantOp::create(
rewriter, loc, i32Ty,
version | static_cast<uint32_t>(
getDataDescriptorKind(DataDescriptor::AccDataDescOpenACC)));
baseDescriptor = LLVM::InsertValueOp::create(
rewriter, loc, baseDescriptorType, baseDescriptor, versionValue,
ArrayRef<int64_t>{0});
Type descriptorType = getDataDescriptorType(
context, DataDescriptor::AccDataDescOpenACC, baseDescriptorType);
Value descriptor = LLVM::ZeroOp::create(rewriter, loc, descriptorType);
Value rank = LLVM::ConstantOp::create(rewriter, loc, i8Ty, bounds.size());
SmallVector<Value> lowerBounds;
SmallVector<Value> upperBounds;
SmallVector<Value> extents;
SmallVector<Value> strides;
SmallVector<Value> starts;
for (Value boundValue : bounds) {
auto bound = cast<DataBoundsOp>(boundValue.getDefiningOp());
lowerBounds.push_back(
getBoundValue(bound.getLowerbound(), bound.getLoc(), rewriter));
upperBounds.push_back(
getBoundValue(bound.getUpperbound(), bound.getLoc(), rewriter));
Value sourceExtent =
bound.getSourceExtent() ? bound.getSourceExtent() : bound.getExtent();
extents.push_back(getBoundValue(sourceExtent, bound.getLoc(), rewriter));
Value stride = getBoundValue(bound.getStride(), bound.getLoc(), rewriter);
if (!bound.getStrideInBytes())
stride =
LLVM::MulOp::create(rewriter, bound.getLoc(), stride, elementSize);
strides.push_back(stride);
starts.push_back(
getBoundValue(bound.getStartIdx(), bound.getLoc(), rewriter));
}
Value lowerBoundsArray =
createACCDataArray(loc, i64Ty, lowerBounds, rewriter);
Value upperBoundsArray =
createACCDataArray(loc, i64Ty, upperBounds, rewriter);
Value extentsArray = createACCDataArray(loc, i64Ty, extents, rewriter);
Value stridesArray = createACCDataArray(loc, i64Ty, strides, rewriter);
Value startsArray = createACCDataArray(loc, i64Ty, starts, rewriter);
using Field = AccDataDescOpenACCField;
auto insert = [&](Value value, Field field) {
descriptor = LLVM::InsertValueOp::create(
rewriter, loc, descriptorType, descriptor, value,
ArrayRef<int64_t>{getDataDescriptorFieldIndex(field)});
};
insert(baseDescriptor, Field::Base);
insert(rank, Field::Rank);
insert(elementSize, Field::ElementSize);
insert(lowerBoundsArray, Field::LowerBounds);
insert(upperBoundsArray, Field::UpperBounds);
insert(extentsArray, Field::Extents);
insert(stridesArray, Field::StridesInBytes);
insert(startsArray, Field::StartIndices);
Value one = LLVM::ConstantOp::create(rewriter, loc, i32Ty, 1);
Value storage =
LLVM::AllocaOp::create(rewriter, loc, ptrTy, descriptorType, one);
LLVM::StoreOp::create(rewriter, loc, descriptor, storage);
return storage;
}
Value mlir::createACCMemRefDescriptorWrapperArg(
Location loc, MemRefType memrefType, Value convertedMemref,
ValueRange bounds, Value elementSize, ConversionPatternRewriter &rewriter) {
MLIRContext *context = rewriter.getContext();
Type i8Ty = rewriter.getI8Type();
Type i32Ty = rewriter.getI32Type();
Type ptrTy = LLVM::LLVMPointerType::get(context);
using Field = AccDataDescMemRefField;
Type descriptorType =
getDataDescriptorType(context, DataDescriptor::AccDataDescMemRef);
auto version = static_cast<uint32_t>(
getDataDescriptorKind(DataDescriptor::AccDataDescMemRef));
Value descriptor = LLVM::ZeroOp::create(rewriter, loc, descriptorType);
Value rank =
LLVM::ConstantOp::create(rewriter, loc, i8Ty, memrefType.getRank());
auto insert = [&](Value value, Field field) {
descriptor = LLVM::InsertValueOp::create(
rewriter, loc, descriptorType, descriptor, value,
ArrayRef<int64_t>{getDataDescriptorFieldIndex(field)});
};
insert(rank, Field::Rank);
insert(elementSize, Field::ElementSize);
insert(getPointer(loc, convertedMemref, rewriter), Field::MemRefDescriptor);
if (!bounds.empty())
return createACCDataDescriptor(loc, descriptor, descriptorType, version,
bounds, elementSize, rewriter);
insert(LLVM::ConstantOp::create(rewriter, loc, i32Ty, version),
Field::Version);
Value one = LLVM::ConstantOp::create(rewriter, loc, i32Ty, 1);
Value storage =
LLVM::AllocaOp::create(rewriter, loc, ptrTy, descriptorType, one);
LLVM::StoreOp::create(rewriter, loc, descriptor, storage);
return storage;
}
Value mlir::createACCArgumentDescriptor(Operation *mapOp,
Value convertedOperand,
OpenACCSupport &accSupport,
ConversionPatternRewriter &rewriter) {
Location loc = mapOp->getLoc();
MLIRContext *context = rewriter.getContext();
Type i32Ty = rewriter.getI32Type();
Type ptrTy = LLVM::LLVMPointerType::get(context);
SmallVector<Value> bounds = acc::getBounds(mapOp);
DataDescKind descKind = acc::getDataDescKind(mapOp);
// Only a descriptor stating bounds reads the element size, so it is
// materialized where it is used, leaving no constant behind for a mapping
// that needs no descriptor at all.
auto elementSize = [&] {
return getElementSize(mapOp, accSupport, rewriter);
};
Value var = acc::getVar(mapOp);
if (var && isa<MemRefType>(var.getType())) {
auto memrefType = cast<MemRefType>(var.getType());
return createACCMemRefDescriptorWrapperArg(
loc, memrefType, convertedOperand, bounds, elementSize(), rewriter);
}
if (bitEnumContainsAny(descKind, DataDescKind::cfi)) {
using Field = AccDataDescCFIField;
Type descriptorType =
getDataDescriptorType(context, DataDescriptor::AccDataDescCFI);
auto version = static_cast<uint32_t>(
getDataDescriptorKind(DataDescriptor::AccDataDescCFI));
Value descriptor = LLVM::ZeroOp::create(rewriter, loc, descriptorType);
Value descriptorStorage = remap(acc::getDesc(mapOp), rewriter);
if (!descriptorStorage)
descriptorStorage = convertedOperand;
descriptor = LLVM::InsertValueOp::create(
rewriter, loc, descriptorType, descriptor,
getPointer(loc, descriptorStorage, rewriter),
ArrayRef<int64_t>{getDataDescriptorFieldIndex(Field::CFIDescriptor)});
if (!bounds.empty())
return createACCDataDescriptor(loc, descriptor, descriptorType, version,
bounds, elementSize(), rewriter);
Value versionValue =
LLVM::ConstantOp::create(rewriter, loc, i32Ty, version);
descriptor = LLVM::InsertValueOp::create(
rewriter, loc, descriptorType, descriptor, versionValue,
ArrayRef<int64_t>{getDataDescriptorFieldIndex(Field::Version)});
Value one = LLVM::ConstantOp::create(rewriter, loc, i32Ty, 1);
Value storage =
LLVM::AllocaOp::create(rewriter, loc, ptrTy, descriptorType, one);
LLVM::StoreOp::create(rewriter, loc, descriptor, storage);
return storage;
}
if (!bounds.empty()) {
Type descriptorType =
getDataDescriptorType(context, DataDescriptor::AccDataDescGeneric);
auto version = static_cast<uint32_t>(
getDataDescriptorKind(DataDescriptor::AccDataDescGeneric));
Value descriptor = LLVM::ZeroOp::create(rewriter, loc, descriptorType);
return createACCDataDescriptor(loc, descriptor, descriptorType, version,
bounds, elementSize(), rewriter);
}
return LLVM::ZeroOp::create(rewriter, loc, ptrTy);
}
SmallVector<Value> mlir::ACCDataRuntimeArgs::getCallArgs() const {
return {ident, flags, deviceType, argNum, argBasePtrs, argPtrs,
argSizes, argTypes, argNames, argMappers, argDescs};
}
LogicalResult mlir::emitACCDataRuntimeArgs(
Location loc, ValueRange mappingOperands, ValueRange convertedOperands,
ConversionPatternRewriter &rewriter, Region &globalSymbolRegion,
acc::OpenACCSupport &accSupport, const acc::ACCRuntimeCallConfig &config,
ACCDataRuntimeArgs &runtimeArgs, ACCDataCallKind callKind,
SymbolTable *symbolTable) {
if (mappingOperands.size() != convertedOperands.size())
return failure();
Type i32Ty = rewriter.getI32Type();
Type i64Ty = rewriter.getI64Type();
Type ptrTy = LLVM::LLVMPointerType::get(rewriter.getContext());
runtimeArgs.ident =
createIdent(loc, getParentFunctionName(mappingOperands), rewriter,
globalSymbolRegion, config, symbolTable);
runtimeArgs.flags = constantI64(loc, 0, rewriter);
runtimeArgs.deviceType = constantI64(
loc, config.getDeviceTypeRuntimeValue(DeviceType::None), rewriter);
runtimeArgs.argMappers = LLVM::ZeroOp::create(rewriter, loc, ptrTy);
// An aggregate passed by value is decomposed into one argument per field by
// the target ABI, so the runtime is told about one object per field as well.
// Each of them keeps the map entry of the aggregate it came from.
SmallVector<std::pair<Value, Value>> mappedObjects;
for (auto [operand, converted] :
llvm::zip_equal(mappingOperands, convertedOperands)) {
auto structType = dyn_cast<LLVM::LLVMStructType>(converted.getType());
if (structType && !structType.isIdentified() &&
structType.getBody().size() > 1) {
for (unsigned field = 0, fields = structType.getBody().size();
field != fields; ++field)
mappedObjects.emplace_back(
operand,
LLVM::ExtractValueOp::create(rewriter, loc, converted, field));
continue;
}
mappedObjects.emplace_back(operand, converted);
}
runtimeArgs.argNum =
LLVM::ConstantOp::create(rewriter, loc, i32Ty, mappedObjects.size());
SmallVector<Value> bases;
SmallVector<Value> pointers;
SmallVector<Value> sizes;
SmallVector<Value> types;
SmallVector<Value> names;
SmallVector<Value> descriptors;
for (auto [operand, converted] : mappedObjects) {
Operation *mapOp = operand.getDefiningOp();
if (!mapOp || !isa<MapInfoOp, ACC_DATA_ENTRY_OPS>(mapOp))
return failure();
std::optional<MapFlags> mapFlags = computePackedMapFlags(mapOp);
if (!mapFlags)
return failure();
*mapFlags = config.postProcessMapFlags(mapOp, *mapFlags);
LLVM_DEBUG(llvm::dbgs() << "mapping " << *mapOp << "\n as "
<< config.formatMapFlags(*mapFlags) << "\n");
Location mapLoc = mapOp->getLoc();
pointers.push_back(getMappedObjectPointer(mapLoc, operand, converted,
*mapFlags, rewriter));
Value base;
if (Value attach = acc::getVarPtrPtr(mapOp))
base = remap(attach, rewriter);
else if (bitEnumContainsAny(*mapFlags, MapFlags::ptr_and_obj))
base = remap(acc::getDesc(mapOp), rewriter);
// An object that only lives on the device has no host address to state as
// its base.
if (bitEnumContainsAny(*mapFlags, MapFlags::device_resident))
base = {};
bases.push_back(getPointer(mapLoc, base, rewriter));
// A local object that lives on the device is torn down when the region
// that declared it ends, which the runtime only does for a mapping that
// states it is deleted.
if (callKind == ACCDataCallKind::DataExit &&
bitEnumContainsAny(*mapFlags, MapFlags::device_resident)) {
Value var = acc::getVar(mapOp);
if (var &&
!isa_and_nonnull<AddressOfGlobalOpInterface>(var.getDefiningOp()))
*mapFlags = *mapFlags | MapFlags::delete_;
}
sizes.push_back(
getMapSize(mapOp, converted, *mapFlags, accSupport, rewriter));
types.push_back(constantI64(
mapLoc, config.getMapFlagsRuntimeValue(*mapFlags), rewriter));
// The runtime resolves an object that lives on the device against the
// symbols of the binary by this name, so it is the name the object is
// emitted under rather than the one the source spells.
VariableNameConfig nameConfig;
nameConfig.preferDemangledName = false;
std::string name = accSupport.getVariableName(operand, nameConfig);
if (name.empty()) {
names.push_back(LLVM::ZeroOp::create(rewriter, mapLoc, ptrTy));
} else {
names.push_back(getOrCreateGlobalString(
mapLoc, rewriter, getInternalGlobalName("var_name", name), name,
globalSymbolRegion, symbolTable));
}
descriptors.push_back(
createACCArgumentDescriptor(mapOp, converted, accSupport, rewriter));
}
runtimeArgs.argBasePtrs = createACCDataArray(loc, ptrTy, bases, rewriter);
runtimeArgs.argPtrs = createACCDataArray(loc, ptrTy, pointers, rewriter);
runtimeArgs.argSizes = createACCDataArray(loc, i64Ty, sizes, rewriter);
runtimeArgs.argTypes = createACCDataArray(loc, i64Ty, types, rewriter);
runtimeArgs.argNames = createACCDataArray(loc, ptrTy, names, rewriter);
runtimeArgs.argDescs = createACCDataArray(loc, ptrTy, descriptors, rewriter);
return success();
}