//===- DataLayoutInterfaces.cpp - Data Layout Interface Implementation ----===// // // 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 "mlir/Interfaces/DataLayoutInterfaces.h" #include "mlir/IR/BuiltinDialect.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Operation.h" #include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/MathExtras.h" using namespace mlir; //===----------------------------------------------------------------------===// // Default implementations //===----------------------------------------------------------------------===// /// Reports that the given type is missing the data layout information and /// exits. [[noreturn]] static void reportMissingDataLayout(Type type) { std::string message; llvm::raw_string_ostream os(message); os << "neither the scoping op nor the type class provide data layout " "information for " << type; llvm::report_fatal_error(Twine(os.str())); } /// Returns the bitwidth of the index type if specified in the param list. /// Assumes 64-bit index otherwise. static uint64_t getIndexBitwidth(DataLayoutEntryListRef params) { if (params.empty()) return 64; auto attr = cast(params.front().getValue()); return attr.getValue().getZExtValue(); } llvm::TypeSize mlir::detail::getDefaultTypeSize(Type type, const DataLayout &dataLayout, ArrayRef params) { llvm::TypeSize bits = getDefaultTypeSizeInBits(type, dataLayout, params); return divideCeil(bits, 8); } llvm::TypeSize mlir::detail::getDefaultTypeSizeInBits(Type type, const DataLayout &dataLayout, DataLayoutEntryListRef params) { if (isa(type)) return llvm::TypeSize::getFixed(type.getIntOrFloatBitWidth()); if (auto ctype = dyn_cast(type)) { Type et = ctype.getElementType(); uint64_t innerAlignment = getDefaultPreferredAlignment(et, dataLayout, params) * 8; llvm::TypeSize innerSize = getDefaultTypeSizeInBits(et, dataLayout, params); // Include padding required to align the imaginary value in the complex // type. return llvm::alignTo(innerSize, innerAlignment) + innerSize; } // Index is an integer of some bitwidth. if (isa(type)) return dataLayout.getTypeSizeInBits( IntegerType::get(type.getContext(), getIndexBitwidth(params))); // Sizes of vector types are rounded up to those of types with closest // power-of-two number of elements in the innermost dimension. We also assume // there is no bit-packing at the moment element sizes are taken in bytes and // multiplied with 8 bits. // TODO: make this extensible. if (auto vecType = dyn_cast(type)) return vecType.getNumElements() / vecType.getShape().back() * llvm::PowerOf2Ceil(vecType.getShape().back()) * dataLayout.getTypeSize(vecType.getElementType()) * 8; if (auto typeInterface = dyn_cast(type)) return typeInterface.getTypeSizeInBits(dataLayout, params); reportMissingDataLayout(type); } static DataLayoutEntryInterface findEntryForIntegerType(IntegerType intType, ArrayRef params) { assert(!params.empty() && "expected non-empty parameter list"); std::map sortedParams; for (DataLayoutEntryInterface entry : params) { sortedParams.insert(std::make_pair( entry.getKey().get().getIntOrFloatBitWidth(), entry)); } auto iter = sortedParams.lower_bound(intType.getWidth()); if (iter == sortedParams.end()) iter = std::prev(iter); return iter->second; } constexpr const static uint64_t kDefaultBitsInByte = 8u; static uint64_t extractABIAlignment(DataLayoutEntryInterface entry) { auto values = cast(entry.getValue()).getValues(); return static_cast(*values.begin()) / kDefaultBitsInByte; } static uint64_t getIntegerTypeABIAlignment(IntegerType intType, ArrayRef params) { constexpr uint64_t kDefaultSmallIntAlignment = 4u; constexpr unsigned kSmallIntSize = 64; if (params.empty()) { return intType.getWidth() < kSmallIntSize ? llvm::PowerOf2Ceil( llvm::divideCeil(intType.getWidth(), kDefaultBitsInByte)) : kDefaultSmallIntAlignment; } return extractABIAlignment(findEntryForIntegerType(intType, params)); } static uint64_t getFloatTypeABIAlignment(FloatType fltType, const DataLayout &dataLayout, ArrayRef params) { assert(params.size() <= 1 && "at most one data layout entry is expected for " "the singleton floating-point type"); if (params.empty()) return llvm::PowerOf2Ceil(dataLayout.getTypeSize(fltType).getFixedValue()); return extractABIAlignment(params[0]); } uint64_t mlir::detail::getDefaultABIAlignment( Type type, const DataLayout &dataLayout, ArrayRef params) { // Natural alignment is the closest power-of-two number above. if (isa(type)) return llvm::PowerOf2Ceil(dataLayout.getTypeSize(type)); if (auto fltType = dyn_cast(type)) return getFloatTypeABIAlignment(fltType, dataLayout, params); // Index is an integer of some bitwidth. if (isa(type)) return dataLayout.getTypeABIAlignment( IntegerType::get(type.getContext(), getIndexBitwidth(params))); if (auto intType = dyn_cast(type)) return getIntegerTypeABIAlignment(intType, params); if (auto ctype = dyn_cast(type)) return getDefaultABIAlignment(ctype.getElementType(), dataLayout, params); if (auto typeInterface = dyn_cast(type)) return typeInterface.getABIAlignment(dataLayout, params); reportMissingDataLayout(type); } static uint64_t extractPreferredAlignment(DataLayoutEntryInterface entry) { auto values = cast(entry.getValue()).getValues(); return *std::next(values.begin(), values.size() - 1) / kDefaultBitsInByte; } static uint64_t getIntegerTypePreferredAlignment(IntegerType intType, const DataLayout &dataLayout, ArrayRef params) { if (params.empty()) return llvm::PowerOf2Ceil(dataLayout.getTypeSize(intType).getFixedValue()); return extractPreferredAlignment(findEntryForIntegerType(intType, params)); } static uint64_t getFloatTypePreferredAlignment(FloatType fltType, const DataLayout &dataLayout, ArrayRef params) { assert(params.size() <= 1 && "at most one data layout entry is expected for " "the singleton floating-point type"); if (params.empty()) return dataLayout.getTypeABIAlignment(fltType); return extractPreferredAlignment(params[0]); } uint64_t mlir::detail::getDefaultPreferredAlignment( Type type, const DataLayout &dataLayout, ArrayRef params) { // Preferred alignment is same as natural for floats and vectors. if (isa(type)) return dataLayout.getTypeABIAlignment(type); if (auto fltType = dyn_cast(type)) return getFloatTypePreferredAlignment(fltType, dataLayout, params); // Preferred alignment is the closest power-of-two number above for integers // (ABI alignment may be smaller). if (auto intType = dyn_cast(type)) return getIntegerTypePreferredAlignment(intType, dataLayout, params); if (isa(type)) { return dataLayout.getTypePreferredAlignment( IntegerType::get(type.getContext(), getIndexBitwidth(params))); } if (auto ctype = dyn_cast(type)) return getDefaultPreferredAlignment(ctype.getElementType(), dataLayout, params); if (auto typeInterface = dyn_cast(type)) return typeInterface.getPreferredAlignment(dataLayout, params); reportMissingDataLayout(type); } // Returns the memory space used for allocal operations if specified in the // given entry. If the entry is empty the default memory space represented by // an empty attribute is returned. Attribute mlir::detail::getDefaultAllocaMemorySpace(DataLayoutEntryInterface entry) { if (entry == DataLayoutEntryInterface()) { return Attribute(); } return entry.getValue(); } // Returns the memory space used for the program memory space. if // specified in the given entry. If the entry is empty the default // memory space represented by an empty attribute is returned. Attribute mlir::detail::getDefaultProgramMemorySpace(DataLayoutEntryInterface entry) { if (entry == DataLayoutEntryInterface()) { return Attribute(); } return entry.getValue(); } // Returns the memory space used for global the global memory space. if // specified in the given entry. If the entry is empty the default memory // space represented by an empty attribute is returned. Attribute mlir::detail::getDefaultGlobalMemorySpace(DataLayoutEntryInterface entry) { if (entry == DataLayoutEntryInterface()) { return Attribute(); } return entry.getValue(); } // Returns the stack alignment if specified in the given entry. If the entry is // empty the default alignment zero is returned. uint64_t mlir::detail::getDefaultStackAlignment(DataLayoutEntryInterface entry) { if (entry == DataLayoutEntryInterface()) return 0; auto value = cast(entry.getValue()); return value.getValue().getZExtValue(); } DataLayoutEntryList mlir::detail::filterEntriesForType(DataLayoutEntryListRef entries, TypeID typeID) { return llvm::to_vector<4>(llvm::make_filter_range( entries, [typeID](DataLayoutEntryInterface entry) { auto type = llvm::dyn_cast_if_present(entry.getKey()); return type && type.getTypeID() == typeID; })); } DataLayoutEntryInterface mlir::detail::filterEntryForIdentifier(DataLayoutEntryListRef entries, StringAttr id) { const auto *it = llvm::find_if(entries, [id](DataLayoutEntryInterface entry) { if (!entry.getKey().is()) return false; return entry.getKey().get() == id; }); return it == entries.end() ? DataLayoutEntryInterface() : *it; } static DataLayoutSpecInterface getSpec(Operation *operation) { return llvm::TypeSwitch(operation) .Case( [&](auto op) { return op.getDataLayoutSpec(); }) .Default([](Operation *) { llvm_unreachable("expected an op with data layout spec"); return DataLayoutSpecInterface(); }); } /// Populates `opsWithLayout` with the list of proper ancestors of `leaf` that /// are either modules or implement the `DataLayoutOpInterface`. static void collectParentLayouts(Operation *leaf, SmallVectorImpl &specs, SmallVectorImpl *opLocations = nullptr) { if (!leaf) return; for (Operation *parent = leaf->getParentOp(); parent != nullptr; parent = parent->getParentOp()) { llvm::TypeSwitch(parent) .Case([&](ModuleOp op) { // Skip top-level module op unless it has a layout. Top-level module // without layout is most likely the one implicitly added by the // parser and it doesn't have location. Top-level null specification // would have had the same effect as not having a specification at all // (using type defaults). if (!op->getParentOp() && !op.getDataLayoutSpec()) return; specs.push_back(op.getDataLayoutSpec()); if (opLocations) opLocations->push_back(op.getLoc()); }) .Case([&](DataLayoutOpInterface op) { specs.push_back(op.getDataLayoutSpec()); if (opLocations) opLocations->push_back(op.getLoc()); }); } } /// Returns a layout spec that is a combination of the layout specs attached /// to the given operation and all its ancestors. static DataLayoutSpecInterface getCombinedDataLayout(Operation *leaf) { if (!leaf) return {}; assert((isa(leaf)) && "expected an op with data layout spec"); SmallVector opsWithLayout; SmallVector specs; collectParentLayouts(leaf, specs); // Fast track if there are no ancestors. if (specs.empty()) return getSpec(leaf); // Create the list of non-null specs (null/missing specs can be safely // ignored) from the outermost to the innermost. auto nonNullSpecs = llvm::to_vector<2>(llvm::make_filter_range( llvm::reverse(specs), [](DataLayoutSpecInterface iface) { return iface != nullptr; })); // Combine the specs using the innermost as anchor. if (DataLayoutSpecInterface current = getSpec(leaf)) return current.combineWith(nonNullSpecs); if (nonNullSpecs.empty()) return {}; return nonNullSpecs.back().combineWith( llvm::ArrayRef(nonNullSpecs).drop_back()); } LogicalResult mlir::detail::verifyDataLayoutOp(Operation *op) { DataLayoutSpecInterface spec = getSpec(op); // The layout specification may be missing and it's fine. if (!spec) return success(); if (failed(spec.verifySpec(op->getLoc()))) return failure(); if (!getCombinedDataLayout(op)) { InFlightDiagnostic diag = op->emitError() << "data layout does not combine with layouts of enclosing ops"; SmallVector specs; SmallVector opLocations; collectParentLayouts(op, specs, &opLocations); for (Location loc : opLocations) diag.attachNote(loc) << "enclosing op with data layout"; return diag; } return success(); } llvm::TypeSize mlir::detail::divideCeil(llvm::TypeSize numerator, uint64_t denominator) { uint64_t divided = llvm::divideCeil(numerator.getKnownMinValue(), denominator); return llvm::TypeSize::get(divided, numerator.isScalable()); } //===----------------------------------------------------------------------===// // DataLayout //===----------------------------------------------------------------------===// template void checkMissingLayout(DataLayoutSpecInterface originalLayout, OpTy op) { if (!originalLayout) { assert((!op || !op.getDataLayoutSpec()) && "could not compute layout information for an op (failed to " "combine attributes?)"); } } mlir::DataLayout::DataLayout() : DataLayout(ModuleOp()) {} mlir::DataLayout::DataLayout(DataLayoutOpInterface op) : originalLayout(getCombinedDataLayout(op)), scope(op), allocaMemorySpace(std::nullopt), programMemorySpace(std::nullopt), globalMemorySpace(std::nullopt), stackAlignment(std::nullopt) { #if LLVM_ENABLE_ABI_BREAKING_CHECKS checkMissingLayout(originalLayout, op); collectParentLayouts(op, layoutStack); #endif } mlir::DataLayout::DataLayout(ModuleOp op) : originalLayout(getCombinedDataLayout(op)), scope(op), allocaMemorySpace(std::nullopt), programMemorySpace(std::nullopt), globalMemorySpace(std::nullopt), stackAlignment(std::nullopt) { #if LLVM_ENABLE_ABI_BREAKING_CHECKS checkMissingLayout(originalLayout, op); collectParentLayouts(op, layoutStack); #endif } mlir::DataLayout mlir::DataLayout::closest(Operation *op) { // Search the closest parent either being a module operation or implementing // the data layout interface. while (op) { if (auto module = dyn_cast(op)) return DataLayout(module); if (auto iface = dyn_cast(op)) return DataLayout(iface); op = op->getParentOp(); } return DataLayout(); } void mlir::DataLayout::checkValid() const { #if LLVM_ENABLE_ABI_BREAKING_CHECKS SmallVector specs; collectParentLayouts(scope, specs); assert(specs.size() == layoutStack.size() && "data layout object used, but no longer valid due to the change in " "number of nested layouts"); for (auto pair : llvm::zip(specs, layoutStack)) { Attribute newLayout = std::get<0>(pair); Attribute origLayout = std::get<1>(pair); assert(newLayout == origLayout && "data layout object used, but no longer valid " "due to the change in layout attributes"); } #endif assert(((!scope && !this->originalLayout) || (scope && this->originalLayout == getCombinedDataLayout(scope))) && "data layout object used, but no longer valid due to the change in " "layout spec"); } /// Looks up the value for the given type key in the given cache. If there is no /// such value in the cache, compute it using the given callback and put it in /// the cache before returning. template static T cachedLookup(Type t, DenseMap &cache, function_ref compute) { auto it = cache.find(t); if (it != cache.end()) return it->second; auto result = cache.try_emplace(t, compute(t)); return result.first->second; } llvm::TypeSize mlir::DataLayout::getTypeSize(Type t) const { checkValid(); return cachedLookup(t, sizes, [&](Type ty) { DataLayoutEntryList list; if (originalLayout) list = originalLayout.getSpecForType(ty.getTypeID()); if (auto iface = dyn_cast_or_null(scope)) return iface.getTypeSize(ty, *this, list); return detail::getDefaultTypeSize(ty, *this, list); }); } llvm::TypeSize mlir::DataLayout::getTypeSizeInBits(Type t) const { checkValid(); return cachedLookup(t, bitsizes, [&](Type ty) { DataLayoutEntryList list; if (originalLayout) list = originalLayout.getSpecForType(ty.getTypeID()); if (auto iface = dyn_cast_or_null(scope)) return iface.getTypeSizeInBits(ty, *this, list); return detail::getDefaultTypeSizeInBits(ty, *this, list); }); } uint64_t mlir::DataLayout::getTypeABIAlignment(Type t) const { checkValid(); return cachedLookup(t, abiAlignments, [&](Type ty) { DataLayoutEntryList list; if (originalLayout) list = originalLayout.getSpecForType(ty.getTypeID()); if (auto iface = dyn_cast_or_null(scope)) return iface.getTypeABIAlignment(ty, *this, list); return detail::getDefaultABIAlignment(ty, *this, list); }); } uint64_t mlir::DataLayout::getTypePreferredAlignment(Type t) const { checkValid(); return cachedLookup(t, preferredAlignments, [&](Type ty) { DataLayoutEntryList list; if (originalLayout) list = originalLayout.getSpecForType(ty.getTypeID()); if (auto iface = dyn_cast_or_null(scope)) return iface.getTypePreferredAlignment(ty, *this, list); return detail::getDefaultPreferredAlignment(ty, *this, list); }); } mlir::Attribute mlir::DataLayout::getAllocaMemorySpace() const { checkValid(); if (allocaMemorySpace) return *allocaMemorySpace; DataLayoutEntryInterface entry; if (originalLayout) entry = originalLayout.getSpecForIdentifier( originalLayout.getAllocaMemorySpaceIdentifier( originalLayout.getContext())); if (auto iface = dyn_cast_or_null(scope)) allocaMemorySpace = iface.getAllocaMemorySpace(entry); else allocaMemorySpace = detail::getDefaultAllocaMemorySpace(entry); return *allocaMemorySpace; } mlir::Attribute mlir::DataLayout::getProgramMemorySpace() const { checkValid(); if (programMemorySpace) return *programMemorySpace; DataLayoutEntryInterface entry; if (originalLayout) entry = originalLayout.getSpecForIdentifier( originalLayout.getProgramMemorySpaceIdentifier( originalLayout.getContext())); if (auto iface = dyn_cast_or_null(scope)) programMemorySpace = iface.getProgramMemorySpace(entry); else programMemorySpace = detail::getDefaultProgramMemorySpace(entry); return *programMemorySpace; } mlir::Attribute mlir::DataLayout::getGlobalMemorySpace() const { checkValid(); if (globalMemorySpace) return *globalMemorySpace; DataLayoutEntryInterface entry; if (originalLayout) entry = originalLayout.getSpecForIdentifier( originalLayout.getGlobalMemorySpaceIdentifier( originalLayout.getContext())); if (auto iface = dyn_cast_or_null(scope)) globalMemorySpace = iface.getGlobalMemorySpace(entry); else globalMemorySpace = detail::getDefaultGlobalMemorySpace(entry); return *globalMemorySpace; } uint64_t mlir::DataLayout::getStackAlignment() const { checkValid(); if (stackAlignment) return *stackAlignment; DataLayoutEntryInterface entry; if (originalLayout) entry = originalLayout.getSpecForIdentifier( originalLayout.getStackAlignmentIdentifier( originalLayout.getContext())); if (auto iface = dyn_cast_or_null(scope)) stackAlignment = iface.getStackAlignment(entry); else stackAlignment = detail::getDefaultStackAlignment(entry); return *stackAlignment; } //===----------------------------------------------------------------------===// // DataLayoutSpecInterface //===----------------------------------------------------------------------===// void DataLayoutSpecInterface::bucketEntriesByType( DenseMap &types, DenseMap &ids) { for (DataLayoutEntryInterface entry : getEntries()) { if (auto type = llvm::dyn_cast_if_present(entry.getKey())) types[type.getTypeID()].push_back(entry); else ids[entry.getKey().get()] = entry; } } LogicalResult mlir::detail::verifyDataLayoutSpec(DataLayoutSpecInterface spec, Location loc) { // First, verify individual entries. for (DataLayoutEntryInterface entry : spec.getEntries()) if (failed(entry.verifyEntry(loc))) return failure(); // Second, dispatch verifications of entry groups to types or dialects they // are associated with. DenseMap types; DenseMap ids; spec.bucketEntriesByType(types, ids); for (const auto &kvp : types) { auto sampleType = kvp.second.front().getKey().get(); if (isa(sampleType)) { assert(kvp.second.size() == 1 && "expected one data layout entry for non-parametric 'index' type"); if (!isa(kvp.second.front().getValue())) return emitError(loc) << "expected integer attribute in the data layout entry for " << sampleType; continue; } if (isa(sampleType)) { for (DataLayoutEntryInterface entry : kvp.second) { auto value = dyn_cast(entry.getValue()); if (!value || !value.getElementType().isSignlessInteger(64)) { emitError(loc) << "expected a dense i64 elements attribute in the " "data layout entry " << entry; return failure(); } auto elements = llvm::to_vector<2>(value.getValues()); unsigned numElements = elements.size(); if (numElements < 1 || numElements > 2) { emitError(loc) << "expected 1 or 2 elements in the data layout entry " << entry; return failure(); } uint64_t abi = elements[0]; uint64_t preferred = numElements == 2 ? elements[1] : abi; if (preferred < abi) { emitError(loc) << "preferred alignment is expected to be greater than or equal " "to the abi alignment in data layout entry " << entry; return failure(); } } continue; } if (isa(&sampleType.getDialect())) return emitError(loc) << "unexpected data layout for a built-in type"; auto dlType = dyn_cast(sampleType); if (!dlType) return emitError(loc) << "data layout specified for a type that does not support it"; if (failed(dlType.verifyEntries(kvp.second, loc))) return failure(); } for (const auto &kvp : ids) { StringAttr identifier = kvp.second.getKey().get(); Dialect *dialect = identifier.getReferencedDialect(); // Ignore attributes that belong to an unknown dialect, the dialect may // actually implement the relevant interface but we don't know about that. if (!dialect) continue; const auto *iface = dyn_cast(dialect); if (!iface) { return emitError(loc) << "the '" << dialect->getNamespace() << "' dialect does not support identifier data layout entries"; } if (failed(iface->verifyEntry(kvp.second, loc))) return failure(); } return success(); } #include "mlir/Interfaces/DataLayoutAttrInterface.cpp.inc" #include "mlir/Interfaces/DataLayoutOpInterface.cpp.inc" #include "mlir/Interfaces/DataLayoutTypeInterface.cpp.inc"