[Codegen] Use overflow-checked math in the CPU and GPU alloc size checks - #24745
[Codegen] Use overflow-checked math in the CPU and GPU alloc size checks#24745dimp-pl wants to merge 1 commit into
Conversation
devtbi
left a comment
There was a problem hiding this comment.
right idea, I'd like to see all size calculations unified
| } | ||
|
|
||
| LogicalResult checkedAdd(int64_t lhs, int64_t rhs, int64_t &result) { | ||
| if (llvm::AddOverflow(lhs, rhs, result)) { |
There was a problem hiding this comment.
this is 1) the wrong place for such abstractions and 2) I'm not sure wrapper functions to wrap an if-statement are good design
There was a problem hiding this comment.
Moved it to Codegen/Utils.cpp, borrowing getStaticShapeSizeInBits from your PR #24684.
This guardrail is good, though I think independent of that, we should not have these allocas. Seems like a tiling/fusion bug. Can you create an issue with a reproducer so that we can also take a look at what's causing those? |
`checkStackAllocationSize` accumulates the allocation size in a plain int64_t. Allocas sized by the upper bound of an unbounded dynamic dimension (the util.assume.int default, 2^53-1) overflow it, the wrapped value passes the limit check and the alloca reaches ConvertToLLVM, where its element count folds to llvm.mlir.poison and the program loads and stores through a garbage pointer at runtime. The reproduction was reduced from the quantized ResNet downsample construct: an ONNX model with a dynamic batch doing DequantizeLinear -> 1x1 stride-2 Conv compiles without diagnostics and returns completely wrong results. Move `getStaticShapeSizeInBits` to Codegen/Utils (taken from iree-org#24684) and use it from both LLVMCPUCheckIRBeforeLLVMConversion and GPUCheckResourceUsage. Callers supply the leaf element bit width, which lets the GPU check keep its index-bitwidth handling and the recursion into shaped element types. On the CPU side the remaining scalar steps are overflow-checked in place with `llvm::MulOverflow` / `llvm::AddOverflow`. The pre-existing `checkedAdd`/`checkedMul`/ `checkedAlignTo` wrappers in LLVMCPUAssignWorkgroupLocalMemory.cpp are likewise replaced with `llvm::MulOverflow` / `llvm::AddOverflow`. Signed-off-by: Zmicier Prybysh <zprybysh@baylibre.com>
84fc675 to
486baa5
Compare
|
@egebeysel thanks for the context, #24483/#24627 definitely seem relevant. I opened a new issue with an easy reproducer for this fusion issue -- #24752. |
|
@devtbi i think i addressed all your points, can i ask you to re-review? |
There was a problem hiding this comment.
sorry for taking so long, I missed to publish the review and forgot about it.
Thanks a lot for unifying LLVMCPU and GPU - that will help a lot :)
review in summary: missing a bit of testing coverage + a few small improvements. Let me know if you want/need help.
| auto emitOverflowError = [&]() { | ||
| return allocaOp->emitOpError( | ||
| "stack allocation size overflows 64 bits; the allocation is " | ||
| "unbounded in practice and cannot live on the stack"); |
There was a problem hiding this comment.
- i don't think this unboundedness holds for all cases where this lambda is used? it might be > 64.bit but still bounded
- Probably makes more sense not to use a lambda
| return | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
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
| int allocSize = shapedTypeStaticSize(allocOp, allocType, getIndexBitwidth); | ||
| auto func = allocOp->getParentOfType<mlir::FunctionOpInterface>(); | ||
| FailureOr<int64_t> allocSizeBits = | ||
| getStaticShapeSizeInBits(allocType, [&](Type elementType) -> int64_t { |
There was a problem hiding this comment.
catch-all ref captures should be avoided because of the invisible dependencies they create
| getStaticShapeSizeInBits(allocType, [&](Type elementType) -> int64_t { | ||
| if (elementType.isIndex()) { | ||
| assert(getIndexBitwidth && | ||
| "getIndexBitwidth should have been set earlier"); |
There was a problem hiding this comment.
nit: I'd make this more terse and ... getIndexBitwidth must not be null, or expected....
| allocSize = | ||
| (llvm::divideCeil(allocSize, alignmentInBits) * alignmentInBits); | ||
| } | ||
| cumSize += allocSize / 8; |
|
|
||
| // ----- | ||
|
|
||
| func.func @overflowing_static_alloca() { |
There was a problem hiding this comment.
would be nice to have more coverage here e.g. for the unbounded dynamic dimension mentioned in the PR description
| return IREE::Util::getTypeBitWidth(elementType); | ||
| }); | ||
| if (failed(allocSizeBits)) { | ||
| return emitError(funcOp->getLoc()) |
There was a problem hiding this comment.
in line with CPU, this should be allocOp.emitOpError to emit better diagnostics
| } | ||
|
|
||
| int allocSize = shapedTypeStaticSize(allocOp, allocType, getIndexBitwidth); | ||
| auto func = allocOp->getParentOfType<mlir::FunctionOpInterface>(); |
There was a problem hiding this comment.
I think this can reuse funcOp
| allocSize = | ||
| (llvm::divideCeil(allocSize, alignmentInBits) * alignmentInBits); | ||
| } | ||
| cumSize += allocSize / 8; |
There was a problem hiding this comment.
not modified, but actually, this looks fishy... @AGindinson, I think this should use divideCeil shouldn't it?
checkStackAllocationSizeaccumulates the allocation size in a plain int64_t. Allocas sized by the upper bound of an unbounded dynamic dimension (the util.assume.int default, 2^53-1) overflow it, the wrapped value passes the limit check and the alloca reaches ConvertToLLVM, where its element count folds to llvm.mlir.poison and the program loads and stores through a garbage pointer at runtime.The reproduction was reduced from the quantized ResNet downsample construct: an ONNX model with a dynamic batch doing DequantizeLinear -> 1x1 stride-2 Conv compiles without diagnostics and returns completely wrong results.
Move
getStaticShapeSizeInBitsto Codegen/Utils (taken from #24684) and use it from both LLVMCPUCheckIRBeforeLLVMConversion and GPUCheckResourceUsage. Callers supply the leaf element bit width,which lets the GPU check keep its index-bitwidth handling and the recursion into shaped element types. On the CPU side the remaining scalar steps are overflow-checked in place with
llvm::MulOverflow/llvm::AddOverflow. The pre-existingcheckedAdd/checkedMul/checkedAlignTowrappers in LLVMCPUAssignWorkgroupLocalMemory.cpp are replaced withllvm::MulOverflow/llvm::AddOverflow.