Issue |
77096
|
Summary |
[mlir][bufferization] Double free when using private-function-dynamic-ownership=true
|
Labels |
mlir
|
Assignees |
|
Reporter |
sabauma
|
When using `ownership-based-buffer-deallocation=private-function-dynamic-ownership=true`, the generated deallocations produce a double-free for `memref` function arguments which are not subsequently returned.
## Reproducer
```mlir
// RUN: mlir-opt %s -buffer-deallocation-pipeline=private-function-dynamic-ownership=true -test-lower-to-llvm \
// RUN: | mlir-cpu-runner -entry-point-result=i32
func.func private @private_callee(%arg0: memref<f32>) -> memref<f32> {
%alloc = memref.alloc() : memref<f32>
return %alloc : memref<f32>
}
func.func @caller() -> (f32) {
%alloc = memref.alloc() : memref<f32>
%ret = call @private_callee(%alloc) : (memref<f32>) -> memref<f32>
%val = memref.load %ret[] : memref<f32>
return %val : f32
}
// Driver main function for mlir-cpu-runner
func.func @main() -> i32 {
%res = func.call @caller() : () -> f32
%val = arith.fptosi %res : f32 to i32
return %val : i32
}
```
Executing this example crashes the `mlir-cpu-runner` with the error: `free(): double free detected in tcache 2`.
## What happens
`ownership-based-buffer-deallocation=private-function-dynamic-ownership=true` generates the following code for the caller and callee:
```mlir
func.func private @private_callee(%arg0: memref<f32>, %arg1: i1) -> (memref<f32>, i1) {
%true = arith.constant true
%alloc = memref.alloc() : memref<f32>
%base_buffer, %offset = memref.extract_strided_metadata %arg0 : memref<f32> -> memref<f32>, index
%base_buffer_0, %offset_1 = memref.extract_strided_metadata %alloc : memref<f32> -> memref<f32>, index
%0 = bufferization.dealloc (%base_buffer, %base_buffer_0 : memref<f32>, memref<f32>) if (%arg1, %true) retain (%alloc : memref<f32>)
return %alloc, %0 : memref<f32>, i1
}
func.func @caller() -> f32 {
%true = arith.constant true
%alloc = memref.alloc() : memref<f32>
%0:2 = call @private_callee(%alloc, %true) : (memref<f32>, i1) -> (memref<f32>, i1)
%1 = memref.load %0#0[] : memref<f32>
%base_buffer, %offset = memref.extract_strided_metadata %alloc : memref<f32> -> memref<f32>, index
%base_buffer_0, %offset_1 = memref.extract_strided_metadata %0#0 : memref<f32> -> memref<f32>, index
bufferization.dealloc (%base_buffer, %base_buffer_0 : memref<f32>, memref<f32>) if (%true, %0#1)
return %1 : f32
}
```
Two deallocations are inserted: one in `@private_callee`
```mlir
%0 = bufferization.dealloc (%base_buffer, %base_buffer_0 : memref<f32>, memref<f32>) if (%arg1, %true) retain (%alloc : memref<f32>)
```
and the other in `@caller`
```mlir
bufferization.dealloc (%base_buffer, %base_buffer_0 : memref<f32>, memref<f32>) if (%true, %0#1)
```
In both cases, `%base_buffer` refers to the allocation in `@caller` and the ownership indicator is `true`. At runtime, both `@private_callee` and `@caller` always free the memref value allocated within `@caller`.
## Other observations
Changing caller to the following:
```mlir
func.func @caller() -> (f32) {
%alloc = memref.alloc() : memref<f32>
%ret = call @private_callee(%alloc) : (memref<f32>) -> memref<f32>
// Read from %alloc rather than %ret
%val = memref.load %alloc[] : memref<f32>
return %val : f32
}
```
Results in a read-after-free of `%alloc`, where `%alloc` is still deallocated within `@private_callee`, but has a use after the call.
_______________________________________________
llvm-bugs mailing list
llvm-bugs@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs