-
Notifications
You must be signed in to change notification settings - Fork 990
[Codegen] Use overflow-checked math in the CPU and GPU alloc size checks #24745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,9 @@ | |
|
|
||
| #include "iree/compiler/Codegen/Common/GPU/Passes.h" | ||
| #include "iree/compiler/Codegen/Utils/GPUUtils.h" | ||
| #include "iree/compiler/Codegen/Utils/Utils.h" | ||
| #include "llvm/Support/CommandLine.h" | ||
| #include "llvm/Support/MathExtras.h" | ||
| #include "mlir/Conversion/LLVMCommon/LoweringOptions.h" | ||
| #include "mlir/Dialect/GPU/IR/GPUDialect.h" | ||
| #include "mlir/Interfaces/FunctionInterfaces.h" | ||
|
|
@@ -24,32 +26,6 @@ static unsigned getDatalayoutIndexBitwidth(mlir::FunctionOpInterface func) { | |
| return options.getIndexBitwidth(); | ||
| } | ||
|
|
||
| static int shapedTypeStaticSize( | ||
| memref::AllocOp allocOp, ShapedType shapedType, | ||
| std::function<unsigned(mlir::FunctionOpInterface)> getIndexBitwidth) { | ||
| int allocSize = 1; | ||
| for (auto dimSize : shapedType.getShape()) { | ||
| if (ShapedType::isDynamic(dimSize)) { | ||
| continue; | ||
| } | ||
| allocSize *= dimSize; | ||
| } | ||
| if (auto elementType = dyn_cast<ShapedType>(shapedType.getElementType())) { | ||
| allocSize *= shapedTypeStaticSize(allocOp, elementType, getIndexBitwidth); | ||
| } else { | ||
| auto eltTy = shapedType.getElementType(); | ||
| if (eltTy.isIndex()) { | ||
| auto func = allocOp->getParentOfType<mlir::FunctionOpInterface>(); | ||
| assert(getIndexBitwidth && | ||
| "getIndexBitwidth should have been set earlier"); | ||
| allocSize *= getIndexBitwidth(func); | ||
| } else { | ||
| allocSize *= IREE::Util::getTypeBitWidth(shapedType.getElementType()); | ||
| } | ||
| } | ||
| return allocSize; | ||
| } | ||
|
|
||
| /// Returns success if the total shared memory allocation size is less than the | ||
| /// limit. | ||
| static LogicalResult checkGPUAllocationSize( | ||
|
|
@@ -65,7 +41,7 @@ static LogicalResult checkGPUAllocationSize( | |
| return success(); | ||
| } | ||
|
|
||
| int cumSize = 0; | ||
| int64_t cumSize = 0; | ||
| for (auto allocOp : allocOps) { | ||
| auto allocType = cast<MemRefType>(allocOp.getType()); | ||
| if (!hasSharedMemoryAddressSpace(allocType)) { | ||
|
|
@@ -77,7 +53,24 @@ static LogicalResult checkGPUAllocationSize( | |
| "has unsupported dynamic shared memory allocations"); | ||
| } | ||
|
|
||
| int allocSize = shapedTypeStaticSize(allocOp, allocType, getIndexBitwidth); | ||
| auto func = allocOp->getParentOfType<mlir::FunctionOpInterface>(); | ||
| FailureOr<int64_t> allocSizeBits = | ||
| getStaticShapeSizeInBits(allocType, [&](Type elementType) -> int64_t { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. catch-all ref captures should be avoided because of the invisible dependencies they create |
||
| if (elementType.isIndex()) { | ||
| assert(getIndexBitwidth && | ||
| "getIndexBitwidth should have been set earlier"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I'd make this more terse and ... getIndexBitwidth must not be null, or expected.... |
||
| return getIndexBitwidth(func); | ||
| } | ||
| return IREE::Util::getTypeBitWidth(elementType); | ||
| }); | ||
| if (failed(allocSizeBits)) { | ||
| return emitError(funcOp->getLoc()) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in line with CPU, this should be allocOp.emitOpError to emit better diagnostics |
||
| << "function '" << funcOp.getName() | ||
| << "' shared memory allocation size overflows the size " | ||
| "computation; exceeded the limit of " | ||
| << limit << " bytes"; | ||
| } | ||
| int64_t allocSize = *allocSizeBits; | ||
| if (allocOp.getAlignment()) { | ||
| int64_t alignmentInBits = *allocOp.getAlignment() * 8; | ||
| allocSize = | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,3 +29,13 @@ module { | |
| return | ||
| } | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would be nice to have more coverage here e.g. index-element bitwidth path or the nested-shaped-element recursio mentioned in the PR description |
||
| // ----- | ||
|
|
||
| module { | ||
| // expected-error @+1 {{shared memory allocation size overflows the size computation}} | ||
| func.func @shared_mem_alloc_size_overflow() { | ||
| memref.alloc() : memref<9007199254740991x1024x14x14xf32, #gpu.address_space<workgroup>> | ||
| return | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| #include "iree/compiler/Codegen/LLVMCPU/Utils.h" | ||
| #include "iree/compiler/Codegen/Utils/Utils.h" | ||
| #include "llvm/Support/CommandLine.h" | ||
| #include "llvm/Support/MathExtras.h" | ||
| #include "mlir/Dialect/MemRef/IR/MemRef.h" | ||
| #include "mlir/Dialect/Vector/IR/ScalableValueBoundsConstraintSet.h" | ||
| #include "mlir/Interfaces/ValueBoundsOpInterface.h" | ||
|
|
@@ -71,14 +72,20 @@ checkStackAllocationSize(mlir::FunctionOpInterface funcOp) { | |
| "all stack allocations need to be hoisted to the entry block of the " | ||
| "function"); | ||
| } | ||
| int64_t allocaSize = 1; | ||
| auto emitOverflowError = [&]() { | ||
|
devtbi marked this conversation as resolved.
|
||
| return allocaOp->emitOpError( | ||
| "stack allocation size overflows 64 bits; the allocation is " | ||
| "unbounded in practice and cannot live on the stack"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| }; | ||
| auto allocaType = cast<ShapedType>(allocaOp.getType()); | ||
| for (auto dimSize : allocaType.getShape()) { | ||
| if (ShapedType::isDynamic(dimSize)) { | ||
| continue; | ||
| } | ||
| allocaSize *= dimSize; | ||
| FailureOr<int64_t> staticSizeBits = | ||
| getStaticShapeSizeInBits(allocaType, [](Type elementType) -> int64_t { | ||
| return IREE::Util::getTypeBitWidth(elementType); | ||
| }); | ||
| if (failed(staticSizeBits)) { | ||
| return emitOverflowError(); | ||
| } | ||
| int64_t allocaSize = *staticSizeBits; | ||
| for (auto operand : allocaOp.getDynamicSizes()) { | ||
| // Assume vscale is `clAssumedVscaleValue` for determining if the alloca | ||
| // is within the stack limit. This should always resolve to a constant | ||
|
|
@@ -90,18 +97,26 @@ checkStackAllocationSize(mlir::FunctionOpInterface funcOp) { | |
| /*vscaleMin=*/assumedVscale, | ||
| /*vscaleMax=*/assumedVscale, presburger::BoundType::UB); | ||
| if (succeeded(ub)) { | ||
| allocaSize *= ub->getSize()->baseSize; | ||
| if (llvm::MulOverflow(allocaSize, | ||
| static_cast<int64_t>(ub->getSize()->baseSize), | ||
| allocaSize)) { | ||
| return emitOverflowError(); | ||
| } | ||
| continue; | ||
| } | ||
| return allocaOp.emitOpError("expected no unbounded stack allocations"); | ||
| } | ||
| allocaSize *= IREE::Util::getTypeBitWidth(allocaType.getElementType()); | ||
| if (allocaOp.getAlignment()) { | ||
| int64_t alignmentInBits = *allocaOp.getAlignment() * 8; | ||
| allocaSize = | ||
| (llvm::divideCeil(allocaSize, alignmentInBits) * alignmentInBits); | ||
| int64_t alignedUnits = llvm::divideCeil(allocaSize, alignmentInBits); | ||
| if (llvm::MulOverflow(alignedUnits, alignmentInBits, allocaSize)) { | ||
| return emitOverflowError(); | ||
| } | ||
| } | ||
| if (llvm::AddOverflow(cumSize, allocaSize / 8, cumSize)) { | ||
| return allocaOp->emitOpError( | ||
| "cumulative stack allocation size overflows 64 bits"); | ||
| } | ||
| cumSize += allocaSize / 8; | ||
| } | ||
| if (cumSize > maxAllocationSizeInBytes) { | ||
| return funcOp.emitOpError("exceeded stack allocation limit of ") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,14 @@ func.func @mix_static_and_dynamic_allocas(%arg0: index) { | |
|
|
||
| // ----- | ||
|
|
||
| func.func @overflowing_static_alloca() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would be nice to have more coverage here e.g. for the unbounded dynamic dimension mentioned in the PR description |
||
| // expected-error @+1 {{stack allocation size overflows 64 bits}} | ||
| %0 = memref.alloca() {alignment = 64 : i64} : memref<9007199254740991x1024x14x14xf32> | ||
| return | ||
| } | ||
|
|
||
| // ----- | ||
|
|
||
| func.func @non_entry_bb_allocas() { | ||
| cf.br ^bb1 | ||
| ^bb1() : | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can reuse
funcOp