| 1 | //===-- CodeGen.cpp -- bridge to lower to LLVM ----------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/ |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "flang/Optimizer/CodeGen/FIROpPatterns.h" |
| 14 | #include "mlir/Dialect/OpenMP/OpenMPDialect.h" |
| 15 | #include "llvm/Support/Debug.h" |
| 16 | |
| 17 | static inline mlir::Type getLlvmPtrType(mlir::MLIRContext *context, |
| 18 | unsigned addressSpace = 0) { |
| 19 | return mlir::LLVM::LLVMPointerType::get(context, addressSpace); |
| 20 | } |
| 21 | |
| 22 | static unsigned getTypeDescFieldId(mlir::Type ty) { |
| 23 | auto isArray = mlir::isa<fir::SequenceType>(fir::dyn_cast_ptrOrBoxEleTy(ty)); |
| 24 | return isArray ? kOptTypePtrPosInBox : kDimsPosInBox; |
| 25 | } |
| 26 | |
| 27 | namespace fir { |
| 28 | |
| 29 | ConvertFIRToLLVMPattern::ConvertFIRToLLVMPattern( |
| 30 | llvm::StringRef rootOpName, mlir::MLIRContext *context, |
| 31 | const fir::LLVMTypeConverter &typeConverter, |
| 32 | const fir::FIRToLLVMPassOptions &options, mlir::PatternBenefit benefit) |
| 33 | : ConvertToLLVMPattern(rootOpName, context, typeConverter, benefit), |
| 34 | options(options) {} |
| 35 | |
| 36 | // Convert FIR type to LLVM without turning fir.box<T> into memory |
| 37 | // reference. |
| 38 | mlir::Type |
| 39 | ConvertFIRToLLVMPattern::convertObjectType(mlir::Type firType) const { |
| 40 | if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(firType)) |
| 41 | return lowerTy().convertBoxTypeAsStruct(boxTy); |
| 42 | return lowerTy().convertType(firType); |
| 43 | } |
| 44 | |
| 45 | mlir::LLVM::ConstantOp ConvertFIRToLLVMPattern::genI32Constant( |
| 46 | mlir::Location loc, mlir::ConversionPatternRewriter &rewriter, |
| 47 | int value) const { |
| 48 | mlir::Type i32Ty = rewriter.getI32Type(); |
| 49 | mlir::IntegerAttr attr = rewriter.getI32IntegerAttr(value); |
| 50 | return rewriter.create<mlir::LLVM::ConstantOp>(loc, i32Ty, attr); |
| 51 | } |
| 52 | |
| 53 | mlir::LLVM::ConstantOp ConvertFIRToLLVMPattern::genConstantOffset( |
| 54 | mlir::Location loc, mlir::ConversionPatternRewriter &rewriter, |
| 55 | int offset) const { |
| 56 | mlir::Type ity = lowerTy().offsetType(); |
| 57 | mlir::IntegerAttr cattr = rewriter.getI32IntegerAttr(offset); |
| 58 | return rewriter.create<mlir::LLVM::ConstantOp>(loc, ity, cattr); |
| 59 | } |
| 60 | |
| 61 | /// Perform an extension or truncation as needed on an integer value. Lowering |
| 62 | /// to the specific target may involve some sign-extending or truncation of |
| 63 | /// values, particularly to fit them from abstract box types to the |
| 64 | /// appropriate reified structures. |
| 65 | mlir::Value ConvertFIRToLLVMPattern::integerCast( |
| 66 | mlir::Location loc, mlir::ConversionPatternRewriter &rewriter, |
| 67 | mlir::Type ty, mlir::Value val, bool fold) const { |
| 68 | auto valTy = val.getType(); |
| 69 | // If the value was not yet lowered, lower its type so that it can |
| 70 | // be used in getPrimitiveTypeSizeInBits. |
| 71 | if (!mlir::isa<mlir::IntegerType>(valTy)) |
| 72 | valTy = convertType(valTy); |
| 73 | auto toSize = mlir::LLVM::getPrimitiveTypeSizeInBits(ty); |
| 74 | auto fromSize = mlir::LLVM::getPrimitiveTypeSizeInBits(valTy); |
| 75 | if (fold) { |
| 76 | if (toSize < fromSize) |
| 77 | return rewriter.createOrFold<mlir::LLVM::TruncOp>(loc, ty, val); |
| 78 | if (toSize > fromSize) |
| 79 | return rewriter.createOrFold<mlir::LLVM::SExtOp>(loc, ty, val); |
| 80 | } else { |
| 81 | if (toSize < fromSize) |
| 82 | return rewriter.create<mlir::LLVM::TruncOp>(loc, ty, val); |
| 83 | if (toSize > fromSize) |
| 84 | return rewriter.create<mlir::LLVM::SExtOp>(loc, ty, val); |
| 85 | } |
| 86 | return val; |
| 87 | } |
| 88 | |
| 89 | fir::ConvertFIRToLLVMPattern::TypePair |
| 90 | ConvertFIRToLLVMPattern::getBoxTypePair(mlir::Type firBoxTy) const { |
| 91 | mlir::Type llvmBoxTy = |
| 92 | lowerTy().convertBoxTypeAsStruct(mlir::cast<fir::BaseBoxType>(firBoxTy)); |
| 93 | return TypePair{firBoxTy, llvmBoxTy}; |
| 94 | } |
| 95 | |
| 96 | /// Construct code sequence to extract the specific value from a `fir.box`. |
| 97 | mlir::Value ConvertFIRToLLVMPattern::getValueFromBox( |
| 98 | mlir::Location loc, TypePair boxTy, mlir::Value box, mlir::Type resultTy, |
| 99 | mlir::ConversionPatternRewriter &rewriter, int boxValue) const { |
| 100 | if (mlir::isa<mlir::LLVM::LLVMPointerType>(box.getType())) { |
| 101 | auto pty = getLlvmPtrType(resultTy.getContext()); |
| 102 | auto p = rewriter.create<mlir::LLVM::GEPOp>( |
| 103 | loc, pty, boxTy.llvm, box, |
| 104 | llvm::ArrayRef<mlir::LLVM::GEPArg>{0, boxValue}); |
| 105 | auto fldTy = getBoxEleTy(boxTy.llvm, {boxValue}); |
| 106 | auto loadOp = rewriter.create<mlir::LLVM::LoadOp>(loc, fldTy, p); |
| 107 | auto castOp = integerCast(loc, rewriter, resultTy, loadOp); |
| 108 | attachTBAATag(loadOp, boxTy.fir, nullptr, p); |
| 109 | return castOp; |
| 110 | } |
| 111 | return rewriter.create<mlir::LLVM::ExtractValueOp>(loc, box, boxValue); |
| 112 | } |
| 113 | |
| 114 | /// Method to construct code sequence to get the triple for dimension `dim` |
| 115 | /// from a box. |
| 116 | llvm::SmallVector<mlir::Value, 3> ConvertFIRToLLVMPattern::getDimsFromBox( |
| 117 | mlir::Location loc, llvm::ArrayRef<mlir::Type> retTys, TypePair boxTy, |
| 118 | mlir::Value box, mlir::Value dim, |
| 119 | mlir::ConversionPatternRewriter &rewriter) const { |
| 120 | mlir::Value l0 = |
| 121 | loadDimFieldFromBox(loc, boxTy, box, dim, 0, retTys[0], rewriter); |
| 122 | mlir::Value l1 = |
| 123 | loadDimFieldFromBox(loc, boxTy, box, dim, 1, retTys[1], rewriter); |
| 124 | mlir::Value l2 = |
| 125 | loadDimFieldFromBox(loc, boxTy, box, dim, 2, retTys[2], rewriter); |
| 126 | return {l0, l1, l2}; |
| 127 | } |
| 128 | |
| 129 | llvm::SmallVector<mlir::Value, 3> ConvertFIRToLLVMPattern::getDimsFromBox( |
| 130 | mlir::Location loc, llvm::ArrayRef<mlir::Type> retTys, TypePair boxTy, |
| 131 | mlir::Value box, int dim, mlir::ConversionPatternRewriter &rewriter) const { |
| 132 | mlir::Value l0 = |
| 133 | getDimFieldFromBox(loc, boxTy, box, dim, 0, retTys[0], rewriter); |
| 134 | mlir::Value l1 = |
| 135 | getDimFieldFromBox(loc, boxTy, box, dim, 1, retTys[1], rewriter); |
| 136 | mlir::Value l2 = |
| 137 | getDimFieldFromBox(loc, boxTy, box, dim, 2, retTys[2], rewriter); |
| 138 | return {l0, l1, l2}; |
| 139 | } |
| 140 | |
| 141 | mlir::Value ConvertFIRToLLVMPattern::loadDimFieldFromBox( |
| 142 | mlir::Location loc, TypePair boxTy, mlir::Value box, mlir::Value dim, |
| 143 | int off, mlir::Type ty, mlir::ConversionPatternRewriter &rewriter) const { |
| 144 | assert(mlir::isa<mlir::LLVM::LLVMPointerType>(box.getType()) && |
| 145 | "descriptor inquiry with runtime dim can only be done on descriptor " |
| 146 | "in memory" ); |
| 147 | mlir::LLVM::GEPOp p = genGEP(loc, boxTy.llvm, rewriter, box, 0, |
| 148 | static_cast<int>(kDimsPosInBox), dim, off); |
| 149 | auto loadOp = rewriter.create<mlir::LLVM::LoadOp>(loc, ty, p); |
| 150 | attachTBAATag(loadOp, boxTy.fir, nullptr, p); |
| 151 | return loadOp; |
| 152 | } |
| 153 | |
| 154 | mlir::Value ConvertFIRToLLVMPattern::getDimFieldFromBox( |
| 155 | mlir::Location loc, TypePair boxTy, mlir::Value box, int dim, int off, |
| 156 | mlir::Type ty, mlir::ConversionPatternRewriter &rewriter) const { |
| 157 | if (mlir::isa<mlir::LLVM::LLVMPointerType>(box.getType())) { |
| 158 | mlir::LLVM::GEPOp p = genGEP(loc, boxTy.llvm, rewriter, box, 0, |
| 159 | static_cast<int>(kDimsPosInBox), dim, off); |
| 160 | auto loadOp = rewriter.create<mlir::LLVM::LoadOp>(loc, ty, p); |
| 161 | attachTBAATag(loadOp, boxTy.fir, nullptr, p); |
| 162 | return loadOp; |
| 163 | } |
| 164 | return rewriter.create<mlir::LLVM::ExtractValueOp>( |
| 165 | loc, box, llvm::ArrayRef<std::int64_t>{kDimsPosInBox, dim, off}); |
| 166 | } |
| 167 | |
| 168 | mlir::Value ConvertFIRToLLVMPattern::getStrideFromBox( |
| 169 | mlir::Location loc, TypePair boxTy, mlir::Value box, unsigned dim, |
| 170 | mlir::ConversionPatternRewriter &rewriter) const { |
| 171 | auto idxTy = lowerTy().indexType(); |
| 172 | return getDimFieldFromBox(loc, boxTy, box, dim, kDimStridePos, idxTy, |
| 173 | rewriter); |
| 174 | } |
| 175 | |
| 176 | /// Read base address from a fir.box. Returned address has type ty. |
| 177 | mlir::Value ConvertFIRToLLVMPattern::getBaseAddrFromBox( |
| 178 | mlir::Location loc, TypePair boxTy, mlir::Value box, |
| 179 | mlir::ConversionPatternRewriter &rewriter) const { |
| 180 | mlir::Type resultTy = ::getLlvmPtrType(boxTy.llvm.getContext()); |
| 181 | return getValueFromBox(loc, boxTy, box, resultTy, rewriter, kAddrPosInBox); |
| 182 | } |
| 183 | |
| 184 | mlir::Value ConvertFIRToLLVMPattern::getElementSizeFromBox( |
| 185 | mlir::Location loc, mlir::Type resultTy, TypePair boxTy, mlir::Value box, |
| 186 | mlir::ConversionPatternRewriter &rewriter) const { |
| 187 | return getValueFromBox(loc, boxTy, box, resultTy, rewriter, kElemLenPosInBox); |
| 188 | } |
| 189 | |
| 190 | /// Read base address from a fir.box. Returned address has type ty. |
| 191 | mlir::Value ConvertFIRToLLVMPattern::getRankFromBox( |
| 192 | mlir::Location loc, TypePair boxTy, mlir::Value box, |
| 193 | mlir::ConversionPatternRewriter &rewriter) const { |
| 194 | mlir::Type resultTy = getBoxEleTy(boxTy.llvm, {kRankPosInBox}); |
| 195 | return getValueFromBox(loc, boxTy, box, resultTy, rewriter, kRankPosInBox); |
| 196 | } |
| 197 | |
| 198 | /// Read the extra field from a fir.box. |
| 199 | mlir::Value ConvertFIRToLLVMPattern::getExtraFromBox( |
| 200 | mlir::Location loc, TypePair boxTy, mlir::Value box, |
| 201 | mlir::ConversionPatternRewriter &rewriter) const { |
| 202 | mlir::Type resultTy = getBoxEleTy(boxTy.llvm, {kExtraPosInBox}); |
| 203 | return getValueFromBox(loc, boxTy, box, resultTy, rewriter, kExtraPosInBox); |
| 204 | } |
| 205 | |
| 206 | // Get the element type given an LLVM type that is of the form |
| 207 | // (array|struct|vector)+ and the provided indexes. |
| 208 | mlir::Type ConvertFIRToLLVMPattern::getBoxEleTy( |
| 209 | mlir::Type type, llvm::ArrayRef<std::int64_t> indexes) const { |
| 210 | for (unsigned i : indexes) { |
| 211 | if (auto t = mlir::dyn_cast<mlir::LLVM::LLVMStructType>(type)) { |
| 212 | assert(!t.isOpaque() && i < t.getBody().size()); |
| 213 | type = t.getBody()[i]; |
| 214 | } else if (auto t = mlir::dyn_cast<mlir::LLVM::LLVMArrayType>(type)) { |
| 215 | type = t.getElementType(); |
| 216 | } else if (auto t = mlir::dyn_cast<mlir::VectorType>(type)) { |
| 217 | type = t.getElementType(); |
| 218 | } else { |
| 219 | fir::emitFatalError(mlir::UnknownLoc::get(type.getContext()), |
| 220 | "request for invalid box element type" ); |
| 221 | } |
| 222 | } |
| 223 | return type; |
| 224 | } |
| 225 | |
| 226 | // Return LLVM type of the object described by a fir.box of \p boxType. |
| 227 | mlir::Type ConvertFIRToLLVMPattern::getLlvmObjectTypeFromBoxType( |
| 228 | mlir::Type boxType) const { |
| 229 | mlir::Type objectType = fir::dyn_cast_ptrOrBoxEleTy(boxType); |
| 230 | assert(objectType && "boxType must be a box type" ); |
| 231 | return this->convertType(objectType); |
| 232 | } |
| 233 | |
| 234 | /// Read the address of the type descriptor from a box. |
| 235 | mlir::Value ConvertFIRToLLVMPattern::loadTypeDescAddress( |
| 236 | mlir::Location loc, TypePair boxTy, mlir::Value box, |
| 237 | mlir::ConversionPatternRewriter &rewriter) const { |
| 238 | unsigned typeDescFieldId = getTypeDescFieldId(boxTy.fir); |
| 239 | mlir::Type tdescType = lowerTy().convertTypeDescType(rewriter.getContext()); |
| 240 | return getValueFromBox(loc, boxTy, box, tdescType, rewriter, typeDescFieldId); |
| 241 | } |
| 242 | |
| 243 | // Load the attribute from the \p box and perform a check against \p maskValue |
| 244 | // The final comparison is implemented as `(attribute & maskValue) != 0`. |
| 245 | mlir::Value ConvertFIRToLLVMPattern::genBoxAttributeCheck( |
| 246 | mlir::Location loc, TypePair boxTy, mlir::Value box, |
| 247 | mlir::ConversionPatternRewriter &rewriter, unsigned maskValue) const { |
| 248 | mlir::Type attrTy = rewriter.getI32Type(); |
| 249 | mlir::Value attribute = |
| 250 | getValueFromBox(loc, boxTy, box, attrTy, rewriter, kAttributePosInBox); |
| 251 | mlir::LLVM::ConstantOp attrMask = genConstantOffset(loc, rewriter, maskValue); |
| 252 | auto maskRes = |
| 253 | rewriter.create<mlir::LLVM::AndOp>(loc, attrTy, attribute, attrMask); |
| 254 | mlir::LLVM::ConstantOp c0 = genConstantOffset(loc, rewriter, 0); |
| 255 | return rewriter.create<mlir::LLVM::ICmpOp>(loc, mlir::LLVM::ICmpPredicate::ne, |
| 256 | maskRes, c0); |
| 257 | } |
| 258 | |
| 259 | mlir::Value ConvertFIRToLLVMPattern::computeBoxSize( |
| 260 | mlir::Location loc, TypePair boxTy, mlir::Value box, |
| 261 | mlir::ConversionPatternRewriter &rewriter) const { |
| 262 | auto firBoxType = mlir::dyn_cast<fir::BaseBoxType>(boxTy.fir); |
| 263 | assert(firBoxType && "must be a BaseBoxType" ); |
| 264 | const mlir::DataLayout &dl = lowerTy().getDataLayout(); |
| 265 | if (!firBoxType.isAssumedRank()) |
| 266 | return genConstantOffset(loc, rewriter, dl.getTypeSize(boxTy.llvm)); |
| 267 | fir::BaseBoxType firScalarBoxType = firBoxType.getBoxTypeWithNewShape(0); |
| 268 | mlir::Type llvmScalarBoxType = |
| 269 | lowerTy().convertBoxTypeAsStruct(firScalarBoxType); |
| 270 | llvm::TypeSize scalarBoxSizeCst = dl.getTypeSize(llvmScalarBoxType); |
| 271 | mlir::Value scalarBoxSize = |
| 272 | genConstantOffset(loc, rewriter, scalarBoxSizeCst); |
| 273 | mlir::Value rawRank = getRankFromBox(loc, boxTy, box, rewriter); |
| 274 | mlir::Value rank = |
| 275 | integerCast(loc, rewriter, scalarBoxSize.getType(), rawRank); |
| 276 | mlir::Type llvmDimsType = getBoxEleTy(boxTy.llvm, {kDimsPosInBox, 1}); |
| 277 | llvm::TypeSize sizePerDimCst = dl.getTypeSize(llvmDimsType); |
| 278 | assert((scalarBoxSizeCst + sizePerDimCst == |
| 279 | dl.getTypeSize(lowerTy().convertBoxTypeAsStruct( |
| 280 | firBoxType.getBoxTypeWithNewShape(1)))) && |
| 281 | "descriptor layout requires adding padding for dim field" ); |
| 282 | mlir::Value sizePerDim = genConstantOffset(loc, rewriter, sizePerDimCst); |
| 283 | mlir::Value dimsSize = rewriter.create<mlir::LLVM::MulOp>( |
| 284 | loc, sizePerDim.getType(), sizePerDim, rank); |
| 285 | mlir::Value size = rewriter.create<mlir::LLVM::AddOp>( |
| 286 | loc, scalarBoxSize.getType(), scalarBoxSize, dimsSize); |
| 287 | return size; |
| 288 | } |
| 289 | |
| 290 | // Find the Block in which the alloca should be inserted. |
| 291 | // The order to recursively find the proper block: |
| 292 | // 1. An OpenMP Op that will be outlined. |
| 293 | // 2. An OpenMP or OpenACC Op with one or more regions holding executable code. |
| 294 | // 3. A LLVMFuncOp |
| 295 | // 4. The first ancestor that is one of the above. |
| 296 | mlir::Block *ConvertFIRToLLVMPattern::getBlockForAllocaInsert( |
| 297 | mlir::Operation *op, mlir::Region *parentRegion) const { |
| 298 | if (auto iface = mlir::dyn_cast<mlir::omp::OutlineableOpenMPOpInterface>(op)) |
| 299 | return iface.getAllocaBlock(); |
| 300 | if (auto recipeIface = mlir::dyn_cast<mlir::accomp::RecipeInterface>(op)) |
| 301 | return recipeIface.getAllocaBlock(*parentRegion); |
| 302 | if (auto llvmFuncOp = mlir::dyn_cast<mlir::LLVM::LLVMFuncOp>(op)) |
| 303 | return &llvmFuncOp.front(); |
| 304 | |
| 305 | return getBlockForAllocaInsert(op->getParentOp(), parentRegion); |
| 306 | } |
| 307 | |
| 308 | // Generate an alloca of size 1 for an object of type \p llvmObjectTy in the |
| 309 | // allocation address space provided for the architecture in the DataLayout |
| 310 | // specification. If the address space is different from the devices |
| 311 | // program address space we perform a cast. In the case of most architectures |
| 312 | // the program and allocation address space will be the default of 0 and no |
| 313 | // cast will be emitted. |
| 314 | mlir::Value ConvertFIRToLLVMPattern::genAllocaAndAddrCastWithType( |
| 315 | mlir::Location loc, mlir::Type llvmObjectTy, unsigned alignment, |
| 316 | mlir::ConversionPatternRewriter &rewriter) const { |
| 317 | auto thisPt = rewriter.saveInsertionPoint(); |
| 318 | mlir::Operation *parentOp = rewriter.getInsertionBlock()->getParentOp(); |
| 319 | mlir::Region *parentRegion = rewriter.getInsertionBlock()->getParent(); |
| 320 | mlir::Block *insertBlock = getBlockForAllocaInsert(parentOp, parentRegion); |
| 321 | rewriter.setInsertionPointToStart(insertBlock); |
| 322 | auto size = genI32Constant(loc, rewriter, 1); |
| 323 | unsigned allocaAs = getAllocaAddressSpace(rewriter); |
| 324 | unsigned programAs = getProgramAddressSpace(rewriter); |
| 325 | |
| 326 | mlir::Value al = rewriter.create<mlir::LLVM::AllocaOp>( |
| 327 | loc, ::getLlvmPtrType(llvmObjectTy.getContext(), allocaAs), llvmObjectTy, |
| 328 | size, alignment); |
| 329 | |
| 330 | // if our allocation address space, is not the same as the program address |
| 331 | // space, then we must emit a cast to the program address space before use. |
| 332 | // An example case would be on AMDGPU, where the allocation address space is |
| 333 | // the numeric value 5 (private), and the program address space is 0 |
| 334 | // (generic). |
| 335 | if (allocaAs != programAs) { |
| 336 | al = rewriter.create<mlir::LLVM::AddrSpaceCastOp>( |
| 337 | loc, ::getLlvmPtrType(llvmObjectTy.getContext(), programAs), al); |
| 338 | } |
| 339 | |
| 340 | rewriter.restoreInsertionPoint(thisPt); |
| 341 | return al; |
| 342 | } |
| 343 | |
| 344 | unsigned ConvertFIRToLLVMPattern::getAllocaAddressSpace( |
| 345 | mlir::ConversionPatternRewriter &rewriter) const { |
| 346 | mlir::Operation *parentOp = rewriter.getInsertionBlock()->getParentOp(); |
| 347 | assert(parentOp != nullptr && |
| 348 | "expected insertion block to have parent operation" ); |
| 349 | if (auto module = parentOp->getParentOfType<mlir::ModuleOp>()) |
| 350 | if (mlir::Attribute addrSpace = |
| 351 | mlir::DataLayout(module).getAllocaMemorySpace()) |
| 352 | return llvm::cast<mlir::IntegerAttr>(addrSpace).getUInt(); |
| 353 | return defaultAddressSpace; |
| 354 | } |
| 355 | |
| 356 | unsigned ConvertFIRToLLVMPattern::getProgramAddressSpace( |
| 357 | mlir::ConversionPatternRewriter &rewriter) const { |
| 358 | mlir::Operation *parentOp = rewriter.getInsertionBlock()->getParentOp(); |
| 359 | assert(parentOp != nullptr && |
| 360 | "expected insertion block to have parent operation" ); |
| 361 | if (auto module = parentOp->getParentOfType<mlir::ModuleOp>()) |
| 362 | if (mlir::Attribute addrSpace = |
| 363 | mlir::DataLayout(module).getProgramMemorySpace()) |
| 364 | return llvm::cast<mlir::IntegerAttr>(addrSpace).getUInt(); |
| 365 | return defaultAddressSpace; |
| 366 | } |
| 367 | |
| 368 | } // namespace fir |
| 369 | |