Skip to content

[Codegen] Use overflow-checked math in the CPU and GPU alloc size checks - #24745

Open
dimp-pl wants to merge 1 commit into
iree-org:mainfrom
dimp-pl:llvmcpu-stack-check-overflow
Open

[Codegen] Use overflow-checked math in the CPU and GPU alloc size checks#24745
dimp-pl wants to merge 1 commit into
iree-org:mainfrom
dimp-pl:llvmcpu-stack-check-overflow

Conversation

@dimp-pl

@dimp-pl dimp-pl commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

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 #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 replaced with llvm::MulOverflow / llvm::AddOverflow.

@dimp-pl
dimp-pl requested a review from hanhanW as a code owner July 22, 2026 14:46

@devtbi devtbi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved it to Codegen/Utils.cpp, borrowing getStaticShapeSizeInBits from your PR #24684.

@egebeysel

Copy link
Copy Markdown
Contributor

JFYI at first glance, these seem relevant:

#24483
#24627
#24532

@egebeysel

Copy link
Copy Markdown
Contributor

Allocas sized by the upper bound of an unbounded dynamic dimension (the util.assume.int default, 2^53-1)

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>
@dimp-pl
dimp-pl force-pushed the llvmcpu-stack-check-overflow branch from 84fc675 to 486baa5 Compare July 23, 2026 16:10
@dimp-pl dimp-pl changed the title [Codegen][LLVMCPU] Use overflow-checked math in the stack allocation size check [Codegen] Use overflow-checked math in the stack allocation size check Jul 23, 2026
@dimp-pl dimp-pl changed the title [Codegen] Use overflow-checked math in the stack allocation size check [Codegen] Use overflow-checked math in the CPU and GPU alloc size checks Jul 23, 2026
@dimp-pl

dimp-pl commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@egebeysel thanks for the context, #24483/#24627 definitely seem relevant. I opened a new issue with an easy reproducer for this fusion issue -- #24752.

@egebeysel
egebeysel requested a review from devtbi July 24, 2026 13:32
@dimp-pl

dimp-pl commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@devtbi i think i addressed all your points, can i ask you to re-review?

@devtbi devtbi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 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
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

int allocSize = shapedTypeStaticSize(allocOp, allocType, getIndexBitwidth);
auto func = allocOp->getParentOfType<mlir::FunctionOpInterface>();
FailureOr<int64_t> allocSizeBits =
getStaticShapeSizeInBits(allocType, [&](Type elementType) -> int64_t {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

getStaticShapeSizeInBits(allocType, [&](Type elementType) -> int64_t {
if (elementType.isIndex()) {
assert(getIndexBitwidth &&
"getIndexBitwidth should have been set earlier");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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....

allocSize =
(llvm::divideCeil(allocSize, alignmentInBits) * alignmentInBits);
}
cumSize += allocSize / 8;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might overflow


// -----

func.func @overflowing_static_alloca() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

return IREE::Util::getTypeBitWidth(elementType);
});
if (failed(allocSizeBits)) {
return emitError(funcOp->getLoc())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

}

int allocSize = shapedTypeStaticSize(allocOp, allocType, getIndexBitwidth);
auto func = allocOp->getParentOfType<mlir::FunctionOpInterface>();

Copy link
Copy Markdown
Contributor

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

allocSize =
(llvm::divideCeil(allocSize, alignmentInBits) * alignmentInBits);
}
cumSize += allocSize / 8;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not modified, but actually, this looks fishy... @AGindinson, I think this should use divideCeil shouldn't it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants