Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ struct OpenACCPointerLikeModel
getPointeeTypeCategory(mlir::Type pointer,
mlir::TypedValue<mlir::acc::PointerLikeType> varPtr,
mlir::Type varType) const;

mlir::Value genAllocate(mlir::Type pointer, mlir::OpBuilder &builder,
mlir::Location loc, llvm::StringRef varName,
mlir::Type varType, mlir::Value originalVar,
bool &needsFree) const;

bool genFree(mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
mlir::TypedValue<mlir::acc::PointerLikeType> varToFree,
mlir::Value allocRes, mlir::Type varType) const;

bool genCopy(mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
mlir::TypedValue<mlir::acc::PointerLikeType> destination,
mlir::TypedValue<mlir::acc::PointerLikeType> source,
mlir::Type varType) const;
};

template <typename T>
Expand Down
241 changes: 241 additions & 0 deletions flang/lib/Optimizer/OpenACC/Support/FIROpenACCTypeInterfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -751,4 +751,245 @@ template bool OpenACCMappableModel<fir::PointerType>::generatePrivateDestroy(
mlir::Type type, mlir::OpBuilder &builder, mlir::Location loc,
mlir::Value privatized) const;

template <typename Ty>
mlir::Value OpenACCPointerLikeModel<Ty>::genAllocate(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
llvm::StringRef varName, mlir::Type varType, mlir::Value originalVar,
bool &needsFree) const {

// Unwrap to get the pointee type.
mlir::Type pointeeTy = fir::dyn_cast_ptrEleTy(pointer);
assert(pointeeTy && "expected pointee type to be extractable");

// Box types are descriptors that contain both metadata and a pointer to data.
// The `genAllocate` API is designed for simple allocations and cannot
// properly handle the dual nature of boxes. Using `generatePrivateInit`
// instead can allocate both the descriptor and its referenced data. For use
// cases that require an empty descriptor storage, potentially this could be
// implemented here.
if (fir::isa_box_type(pointeeTy))
return {};

// Unlimited polymorphic (class(*)) cannot be handled - size unknown
if (fir::isUnlimitedPolymorphicType(pointeeTy))
return {};

// Return null for dynamic size types because the size of the
// allocation cannot be determined simply from the type.
if (fir::hasDynamicSize(pointeeTy))
return {};

// Use heap allocation for fir.heap, stack allocation for others (fir.ref,
// fir.ptr, fir.llvm_ptr). For fir.ptr, which is supposed to represent a
// Fortran pointer type, it feels a bit odd to "allocate" since it is meant
// to point to an existing entity - but one can imagine where a pointee is
// privatized - thus it makes sense to issue an allocate.
mlir::Value allocation;
if (std::is_same_v<Ty, fir::HeapType>) {
needsFree = true;
allocation = fir::AllocMemOp::create(builder, loc, pointeeTy);
} else {
needsFree = false;
allocation = fir::AllocaOp::create(builder, loc, pointeeTy);
}

// Convert to the requested pointer type if needed.
// This means converting from a fir.ref to either a fir.llvm_ptr or a fir.ptr.
// fir.heap is already correct type in this case.
if (allocation.getType() != pointer) {
assert(!(std::is_same_v<Ty, fir::HeapType>) &&
"fir.heap is already correct type because of allocmem");
return fir::ConvertOp::create(builder, loc, pointer, allocation);
}

return allocation;
}

template mlir::Value OpenACCPointerLikeModel<fir::ReferenceType>::genAllocate(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
llvm::StringRef varName, mlir::Type varType, mlir::Value originalVar,
bool &needsFree) const;

template mlir::Value OpenACCPointerLikeModel<fir::PointerType>::genAllocate(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
llvm::StringRef varName, mlir::Type varType, mlir::Value originalVar,
bool &needsFree) const;

template mlir::Value OpenACCPointerLikeModel<fir::HeapType>::genAllocate(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
llvm::StringRef varName, mlir::Type varType, mlir::Value originalVar,
bool &needsFree) const;

template mlir::Value OpenACCPointerLikeModel<fir::LLVMPointerType>::genAllocate(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
llvm::StringRef varName, mlir::Type varType, mlir::Value originalVar,
bool &needsFree) const;

static mlir::Value stripCasts(mlir::Value value, bool stripDeclare = true) {
mlir::Value currentValue = value;

while (currentValue) {
auto *definingOp = currentValue.getDefiningOp();
if (!definingOp)
break;

if (auto convertOp = mlir::dyn_cast<fir::ConvertOp>(definingOp)) {
currentValue = convertOp.getValue();
continue;
}

if (auto viewLike = mlir::dyn_cast<mlir::ViewLikeOpInterface>(definingOp)) {
currentValue = viewLike.getViewSource();
continue;
}

if (stripDeclare) {
if (auto declareOp = mlir::dyn_cast<hlfir::DeclareOp>(definingOp)) {
currentValue = declareOp.getMemref();
continue;
}

if (auto declareOp = mlir::dyn_cast<fir::DeclareOp>(definingOp)) {
currentValue = declareOp.getMemref();
continue;
}
}
break;
}

return currentValue;
}

template <typename Ty>
bool OpenACCPointerLikeModel<Ty>::genFree(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
mlir::TypedValue<mlir::acc::PointerLikeType> varToFree,
mlir::Value allocRes, mlir::Type varType) const {

// Unwrap to get the pointee type.
mlir::Type pointeeTy = fir::dyn_cast_ptrEleTy(pointer);
assert(pointeeTy && "expected pointee type to be extractable");

// Box types contain both a descriptor and data. The `genFree` API
// handles simple deallocations and cannot properly manage both parts.
// Using `generatePrivateDestroy` instead can free both the descriptor and
// its referenced data.
if (fir::isa_box_type(pointeeTy))
return false;

// If pointer type is HeapType, assume it's a heap allocation
if (std::is_same_v<Ty, fir::HeapType>) {
fir::FreeMemOp::create(builder, loc, varToFree);
return true;
}

// Use allocRes if provided to determine the allocation type
mlir::Value valueToInspect = allocRes ? allocRes : varToFree;

// Strip casts and declare operations to find the original allocation
mlir::Value strippedValue = stripCasts(valueToInspect);
mlir::Operation *originalAlloc = strippedValue.getDefiningOp();

// If we found an AllocMemOp (heap allocation), free it
if (mlir::isa_and_nonnull<fir::AllocMemOp>(originalAlloc)) {
mlir::Value toFree = varToFree;
if (!mlir::isa<fir::HeapType>(valueToInspect.getType()))
toFree = fir::ConvertOp::create(
builder, loc,
fir::HeapType::get(varToFree.getType().getElementType()), toFree);
fir::FreeMemOp::create(builder, loc, toFree);
return true;
}

// If we found an AllocaOp (stack allocation), no deallocation needed
if (mlir::isa_and_nonnull<fir::AllocaOp>(originalAlloc))
return true;

// Unable to determine allocation type
return false;
}

template bool OpenACCPointerLikeModel<fir::ReferenceType>::genFree(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
mlir::TypedValue<mlir::acc::PointerLikeType> varToFree,
mlir::Value allocRes, mlir::Type varType) const;

template bool OpenACCPointerLikeModel<fir::PointerType>::genFree(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
mlir::TypedValue<mlir::acc::PointerLikeType> varToFree,
mlir::Value allocRes, mlir::Type varType) const;

template bool OpenACCPointerLikeModel<fir::HeapType>::genFree(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
mlir::TypedValue<mlir::acc::PointerLikeType> varToFree,
mlir::Value allocRes, mlir::Type varType) const;

template bool OpenACCPointerLikeModel<fir::LLVMPointerType>::genFree(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
mlir::TypedValue<mlir::acc::PointerLikeType> varToFree,
mlir::Value allocRes, mlir::Type varType) const;

template <typename Ty>
bool OpenACCPointerLikeModel<Ty>::genCopy(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
mlir::TypedValue<mlir::acc::PointerLikeType> destination,
mlir::TypedValue<mlir::acc::PointerLikeType> source,
mlir::Type varType) const {

// Check that source and destination types match
if (source.getType() != destination.getType())
return false;

// Unwrap to get the pointee type.
mlir::Type pointeeTy = fir::dyn_cast_ptrEleTy(pointer);
assert(pointeeTy && "expected pointee type to be extractable");

// Box types contain both a descriptor and referenced data. The genCopy API
// handles simple copies and cannot properly manage both parts.
if (fir::isa_box_type(pointeeTy))
return false;

// Unlimited polymorphic (class(*)) cannot be handled because source and
// destination types are not known.
if (fir::isUnlimitedPolymorphicType(pointeeTy))
return false;

// Return false for dynamic size types because the copy logic
// cannot be determined simply from the type.
if (fir::hasDynamicSize(pointeeTy))
return false;

if (fir::isa_trivial(pointeeTy)) {
auto loadVal = fir::LoadOp::create(builder, loc, source);
fir::StoreOp::create(builder, loc, loadVal, destination);
} else {
hlfir::AssignOp::create(builder, loc, source, destination);
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that if this interface is meant to handle the fir.ref case, this will copy the data, not the descriptors, which is not in line with the current impl of genAllocate/Free that allocate/free the descriptors.

Side note that there is also a fir.copy type that can lower to memcopy/memmove. It will however not perform any deep copies of allocatable components, nor call the user defined assignments on components that are derived type (I am still not sure we want to call those).

If doing deep copies/calling user defined assignments on components is what you intend, calling hlfir.assign is the right solution.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice catch. I decided to reject fir.ref<fir.box> for now in all of the APIs to prevent this misuse/misunderstanding regarding descriptor and its data.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

}
return true;
}

template bool OpenACCPointerLikeModel<fir::ReferenceType>::genCopy(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
mlir::TypedValue<mlir::acc::PointerLikeType> destination,
mlir::TypedValue<mlir::acc::PointerLikeType> source,
mlir::Type varType) const;

template bool OpenACCPointerLikeModel<fir::PointerType>::genCopy(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
mlir::TypedValue<mlir::acc::PointerLikeType> destination,
mlir::TypedValue<mlir::acc::PointerLikeType> source,
mlir::Type varType) const;

template bool OpenACCPointerLikeModel<fir::HeapType>::genCopy(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
mlir::TypedValue<mlir::acc::PointerLikeType> destination,
mlir::TypedValue<mlir::acc::PointerLikeType> source,
mlir::Type varType) const;

template bool OpenACCPointerLikeModel<fir::LLVMPointerType>::genCopy(
mlir::Type pointer, mlir::OpBuilder &builder, mlir::Location loc,
mlir::TypedValue<mlir::acc::PointerLikeType> destination,
mlir::TypedValue<mlir::acc::PointerLikeType> source,
mlir::Type varType) const;

} // namespace fir::acc
122 changes: 122 additions & 0 deletions flang/test/Fir/OpenACC/pointer-like-interface-alloc.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// RUN: fir-opt %s --split-input-file --pass-pipeline="builtin.module(func.func(test-acc-pointer-like-interface{test-mode=alloc}))" 2>&1 | FileCheck %s

// The tests here use a synthetic hlfir.declare in order to ensure that the hlfir dialect is
// loaded. This is required because the pass used is part of OpenACC test passes outside of
// flang and the APIs being test may generate hlfir even when it does not appear.

func.func @test_ref_scalar_alloc() {
%0 = fir.alloca f32 {test.ptr}
%1:2 = hlfir.declare %0 {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
// CHECK: Successfully generated alloc for operation: %{{.*}} = fir.alloca f32 {test.ptr}
// CHECK: Generated: %{{.*}} = fir.alloca f32
return
}

// -----

func.func @test_ref_static_array_alloc() {
%0 = fir.alloca !fir.array<10x20xf32> {test.ptr}
%var = fir.alloca f32
%1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
// CHECK: Successfully generated alloc for operation: %{{.*}} = fir.alloca !fir.array<10x20xf32> {test.ptr}
// CHECK: Generated: %{{.*}} = fir.alloca !fir.array<10x20xf32>
return
}

// -----

func.func @test_ref_derived_type_alloc() {
%0 = fir.alloca !fir.type<_QTt{i:i32}> {test.ptr}
%var = fir.alloca f32
%1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
// CHECK: Successfully generated alloc for operation: %{{.*}} = fir.alloca !fir.type<_QTt{i:i32}> {test.ptr}
// CHECK: Generated: %{{.*}} = fir.alloca !fir.type<_QTt{i:i32}>
return
}

// -----

func.func @test_heap_scalar_alloc() {
%0 = fir.allocmem f32 {test.ptr}
%var = fir.alloca f32
%1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
// CHECK: Successfully generated alloc for operation: %{{.*}} = fir.allocmem f32 {test.ptr}
// CHECK: Generated: %{{.*}} = fir.allocmem f32
return
}

// -----

func.func @test_heap_static_array_alloc() {
%0 = fir.allocmem !fir.array<10x20xf32> {test.ptr}
%var = fir.alloca f32
%1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
// CHECK: Successfully generated alloc for operation: %{{.*}} = fir.allocmem !fir.array<10x20xf32> {test.ptr}
// CHECK: Generated: %{{.*}} = fir.allocmem !fir.array<10x20xf32>
return
}

// -----

func.func @test_ptr_scalar_alloc() {
%0 = fir.alloca f32
%1 = fir.convert %0 {test.ptr} : (!fir.ref<f32>) -> !fir.ptr<f32>
%2:2 = hlfir.declare %0 {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
// CHECK: Successfully generated alloc for operation
// CHECK: Generated: %{{.*}} = fir.alloca f32
// CHECK: Generated: %{{.*}} = fir.convert %{{.*}} : (!fir.ref<f32>) -> !fir.ptr<f32>
return
}

// -----

func.func @test_llvm_ptr_scalar_alloc() {
%0 = fir.alloca f32
%1 = fir.convert %0 {test.ptr} : (!fir.ref<f32>) -> !fir.llvm_ptr<f32>
%2:2 = hlfir.declare %0 {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
// CHECK: Successfully generated alloc for operation
// CHECK: Generated: %{{.*}} = fir.alloca f32
// CHECK: Generated: %{{.*}} = fir.convert %{{.*}} : (!fir.ref<f32>) -> !fir.llvm_ptr<f32>
return
}

// -----

func.func @test_dynamic_array_alloc_fails(%arg0: !fir.ref<!fir.array<?xf32>>) {
%0 = fir.convert %arg0 {test.ptr} : (!fir.ref<!fir.array<?xf32>>) -> !fir.llvm_ptr<!fir.array<?xf32>>
%var = fir.alloca f32
%1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
// CHECK: Failed to generate alloc for operation: %{{.*}} = fir.convert %{{.*}} {test.ptr} : (!fir.ref<!fir.array<?xf32>>) -> !fir.llvm_ptr<!fir.array<?xf32>>
return
}

// -----

func.func @test_unlimited_polymorphic_alloc_fails() {
%0 = fir.alloca !fir.class<none> {test.ptr}
%var = fir.alloca f32
%1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
// CHECK: Failed to generate alloc for operation: %{{.*}} = fir.alloca !fir.class<none> {test.ptr}
return
}

// -----

func.func @test_dynamic_char_alloc_fails(%arg0: !fir.ref<!fir.char<1,?>>) {
%0 = fir.convert %arg0 {test.ptr} : (!fir.ref<!fir.char<1,?>>) -> !fir.llvm_ptr<!fir.char<1,?>>
%var = fir.alloca f32
%1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
// CHECK: Failed to generate alloc for operation: %{{.*}} = fir.convert %{{.*}} {test.ptr} : (!fir.ref<!fir.char<1,?>>) -> !fir.llvm_ptr<!fir.char<1,?>>
return
}

// -----

func.func @test_static_char_alloc() {
%0 = fir.alloca !fir.char<1,10> {test.ptr}
%var = fir.alloca f32
%1:2 = hlfir.declare %var {uniq_name = "load_hlfir"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
// CHECK: Successfully generated alloc for operation: %{{.*}} = fir.alloca !fir.char<1,10> {test.ptr}
// CHECK: Generated: %{{.*}} = fir.alloca !fir.char<1,10>
return
}
Loading