[flang-commits] [flang] [llvm] [flang] Support static-unit array slices in FIR LoopVersioning (PR #222723)
Sergey Shcherbinin via flang-commits
flang-commits at lists.llvm.org
Sat Sep 12 04:43:28 PDT 2026
https://github.com/SergeyShch01 updated https://github.com/llvm/llvm-project/pull/222723
>From 343d738707960755ce30d280a078046d4fad6d23 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Sun, 6 Sep 2026 09:24:42 +0400
Subject: [PATCH 1/5] [flang] Support static-unit array slices in
LoopVersioning
Add loop versioning for direct full-rank fir.array_coor slices. The fast
variant uses descriptor byte strides, while the fallback preserves the
original sliced accesses. Descriptor-wide preflight validates all accesses
before modifying IR.
This first patch supports a leading section and scalar or static-unit section
dimensions thereafter. Component paths, non-unit steps, nested owners,
storage wrappers, and mixed sliced/slice-free uses remain unsupported.
Follow-up patches will remove these restrictions.
LIT tests cover the supported and rejected forms, ownership, layouts, and
lowering. Executable tests verify generation and correct execution of the
fast path.
This enables vectorization of the hot loop in the SPEC CPU2000 187.facerec
Face Recognition benchmark and improves its performance by about 12% on
NVIDIA Grace.
---
.../loop-versioning-unit-slices-repack.f90 | 63 +
.../Driver/loop-versioning-unit-slices.f90 | 368 ++
flang-rt/test/lit.cfg.py | 4 +
flang-rt/test/lit.site.cfg.py.in | 1 +
.../flang/Optimizer/Transforms/Passes.td | 5 +
flang/lib/Optimizer/Transforms/CMakeLists.txt | 3 +
.../Optimizer/Transforms/LoopVersioning.cpp | 1370 +++++++-
.../Fir/loop-versioning-slices-pointer32.fir | 173 +
.../loop-versioning-slices-target-layout.fir | 343 ++
.../loop-versioning-slices-disabled-nfc.fir | 78 +
...loop-versioning-slices-enclosing-scope.fir | 88 +
...loop-versioning-slices-layout-conflict.fir | 92 +
.../loop-versioning-slices-source.f90 | 306 ++
.../loop-versioning-unit-slices.fir | 3078 +++++++++++++++++
flang/test/Transforms/loop-versioning.fir | 5 +-
.../lib/Optimizer/Transforms/BUILD.bazel | 1 +
16 files changed, 5868 insertions(+), 110 deletions(-)
create mode 100644 flang-rt/test/Driver/loop-versioning-unit-slices-repack.f90
create mode 100644 flang-rt/test/Driver/loop-versioning-unit-slices.f90
create mode 100644 flang/test/Fir/loop-versioning-slices-pointer32.fir
create mode 100644 flang/test/Fir/loop-versioning-slices-target-layout.fir
create mode 100644 flang/test/Transforms/loop-versioning-slices-disabled-nfc.fir
create mode 100644 flang/test/Transforms/loop-versioning-slices-enclosing-scope.fir
create mode 100644 flang/test/Transforms/loop-versioning-slices-layout-conflict.fir
create mode 100644 flang/test/Transforms/loop-versioning-slices-source.f90
create mode 100644 flang/test/Transforms/loop-versioning-unit-slices.fir
diff --git a/flang-rt/test/Driver/loop-versioning-unit-slices-repack.f90 b/flang-rt/test/Driver/loop-versioning-unit-slices-repack.f90
new file mode 100644
index 0000000000000..dfff9a499d91c
--- /dev/null
+++ b/flang-rt/test/Driver/loop-versioning-unit-slices-repack.f90
@@ -0,0 +1,63 @@
+! Test FIR loop versioning by checking the generated fast path and verifying
+! that its execution produces the expected results.
+! Verify slice versioning through a frontend-generated fir.pack_array.
+! REQUIRES: llvm-flang
+! UNSUPPORTED: offload-cuda
+! RUN: %flang -S -O3 -fversion-loops-for-stride \
+! RUN: -frepack-arrays -frepack-arrays-contiguity=whole \
+! RUN: -mmlir --mlir-disable-threading \
+! RUN: -mmlir --mlir-print-ir-after=loop-versioning \
+! RUN: %s -o /dev/null 2>&1 | \
+! RUN: FileCheck %s --check-prefix=VERSIONED --enable-var-scope
+! RUN: %flang %isysroot -L"%libdir" -O3 -fversion-loops-for-stride \
+! RUN: -frepack-arrays -frepack-arrays-contiguity=whole %s -o %t
+! RUN: env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:%libdir" %t | FileCheck %s
+
+module loop_versioning_unit_slices_repack_m
+ implicit none
+contains
+ subroutine fill_repacked(values, indices, record)
+ real, intent(inout) :: values(:, :)
+ integer, intent(in) :: indices(2)
+ character(*), intent(in) :: record
+
+ read(record, *) values(2:3, indices)
+ end subroutine
+end module
+
+program loop_versioning_unit_slices_repack
+ use loop_versioning_unit_slices_repack_m, only: fill_repacked
+ implicit none
+ real :: storage(6, 2), expected(6, 2)
+ integer :: indices(2)
+
+ storage = -1.0
+ expected = -1.0
+ indices = [1, 2]
+
+ ! The actual argument is noncontiguous in its first dimension. Whole-array
+ ! repacking gives the callee a contiguous temporary, and the epilogue must
+ ! copy the values written through the byte fast path back to storage.
+ call fill_repacked(storage(1:5:2, :), indices, '1 2 3 4')
+ expected(3:5:2, 1) = [1.0, 2.0]
+ expected(3:5:2, 2) = [3.0, 4.0]
+ if (any(storage /= expected)) error stop 1
+
+ ! CHECK: PASS
+ print '(A)', 'PASS'
+end program
+
+! The compile-time half proves that the executed repacking configuration uses
+! the packed descriptor in the guarded byte-address fast path.
+! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_repack_mPfill_repacked(
+! VERSIONED: %[[PACKED:.*]] = fir.pack_array %[[ORIGINAL:.*]] heap whole
+! VERSIONED: %[[DECLARED:.*]] = fir.declare %[[PACKED]]
+! VERSIONED: %[[PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
+! VERSIONED-NEXT: fir.if %[[PRED]]
+! VERSIONED: %[[BYTE_BOX:.*]] = fir.convert %[[DECLARED]]
+! VERSIONED-SAME: -> !fir.box<!fir.array<?xi8>>
+! VERSIONED: %[[BASE:.*]] = fir.box_addr %[[BYTE_BOX]]
+! VERSIONED: %[[ADDRESS:.*]] = fir.coordinate_of %[[BASE]],
+! VERSIONED-NEXT: %[[FAST:.*]] = fir.convert %[[ADDRESS]]
+! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[FAST]])
+! VERSIONED: fir.unpack_array %[[PACKED]] to %[[ORIGINAL]] heap
diff --git a/flang-rt/test/Driver/loop-versioning-unit-slices.f90 b/flang-rt/test/Driver/loop-versioning-unit-slices.f90
new file mode 100644
index 0000000000000..2151bb1cabdc2
--- /dev/null
+++ b/flang-rt/test/Driver/loop-versioning-unit-slices.f90
@@ -0,0 +1,368 @@
+! Test FIR loop versioning by checking the generated fast path and verifying
+! that its execution produces the expected results.
+! Verify runtime semantics for source-expressible static-unit slices.
+! The compiler source test separately proves byte fast paths for every
+! distinct positive access and ownership shape exercised below.
+! REQUIRES: llvm-flang
+! UNSUPPORTED: offload-cuda
+! RUN: %flang -S -O3 -fversion-loops-for-stride \
+! RUN: -mmlir --mlir-disable-threading \
+! RUN: -mmlir --mlir-print-ir-after=loop-versioning \
+! RUN: %s -o /dev/null 2>&1 | \
+! RUN: FileCheck %s --check-prefix=VERSIONED --enable-var-scope
+! RUN: %flang -S -O3 -fversion-loops-for-stride \
+! RUN: -fdefault-integer-8 -fdefault-real-8 \
+! RUN: -mmlir --mlir-disable-threading \
+! RUN: -mmlir --mlir-print-ir-after=loop-versioning \
+! RUN: %s -o /dev/null 2>&1 | \
+! RUN: FileCheck %s --check-prefix=VERSIONED --enable-var-scope
+! RUN: %flang %isysroot -L"%libdir" -O3 -fversion-loops-for-stride %s -o %t
+! RUN: env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:%libdir" %t | FileCheck %s
+! RUN: %flang %isysroot -L"%libdir" -O3 -fversion-loops-for-stride \
+! RUN: -fdefault-integer-8 -fdefault-real-8 %s -o %t.wide
+! RUN: env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:%libdir" %t.wide | FileCheck %s
+
+module loop_versioning_unit_slices_m
+ implicit none
+contains
+ subroutine fill_slices(graph, gabor, indices, y, record)
+ real, intent(inout) :: graph(:, :, :), gabor(:, :, :, :)
+ integer, intent(in) :: indices(2)
+ integer(kind=8), intent(in) :: y
+ character(*), intent(in) :: record
+
+ read(record, *) graph(:, :, indices), gabor(:, :, indices, y)
+ end subroutine
+
+ subroutine fill_offset_slices(graph, gabor, indices, y, record)
+ real, intent(inout) :: graph(:, :, :), gabor(:, :, :, :)
+ integer, intent(in) :: indices(2)
+ integer(kind=8), intent(in) :: y
+ character(*), intent(in) :: record
+
+ read(record, *) graph(2:3, 2:3, indices), &
+ gabor(2:3, 2:3, indices, y)
+ end subroutine
+
+ subroutine fill_rank2_patterns(values, lower0, lower1, indices, record)
+ real, intent(inout) :: values(:, :)
+ integer, intent(in) :: lower0, lower1, indices(2)
+ character(*), intent(in) :: record
+
+ read(record, *) values(lower0:lower0 + 1, indices), &
+ values(lower1:lower1 + 1, indices)
+ end subroutine
+
+ subroutine fill_generalized(values, lower0, lower2, indices, record)
+ real(kind=8), intent(inout) :: values(:, :, :)
+ integer, intent(in) :: lower0, lower2, indices(2)
+ character(*), intent(in) :: record
+
+ read(record, *) values(lower0:lower0 + 1, indices, lower2:lower2 + 1)
+ end subroutine
+
+ subroutine fill_constant_scalar(values, indices, record)
+ real, intent(inout) :: values(:, :, :)
+ integer, intent(in) :: indices(2)
+ character(*), intent(in) :: record
+
+ read(record, *) values(2:3, indices, 1)
+ end subroutine
+
+ subroutine fill_isolated_descriptors(good, strided, indices, step, &
+ good_record, strided_record)
+ real, intent(inout) :: good(:, :), strided(:, :)
+ integer, intent(in) :: indices(2), step
+ character(*), intent(in) :: good_record, strided_record
+
+ read(good_record, *) good(2:4, indices)
+ read(strided_record, *) strided(1:5:step, indices)
+ end subroutine
+
+ subroutine fill_sequential_owners(values, indices, first_record, &
+ second_record)
+ real, intent(inout) :: values(:, :)
+ integer, intent(in) :: indices(2)
+ character(*), intent(in) :: first_record, second_record
+
+ read(first_record, *) values(1:2, indices)
+ read(second_record, *) values(4:5, indices)
+ end subroutine
+end module
+
+program loop_versioning_unit_slices
+ use loop_versioning_unit_slices_m, only: fill_constant_scalar, &
+ fill_generalized, fill_isolated_descriptors, fill_offset_slices, &
+ fill_rank2_patterns, fill_sequential_owners, fill_slices
+ implicit none
+ real :: graph(2, 2, 3), gabor(2, 2, 3, 2)
+ real :: graph_storage(4, 4, 3), gabor_storage(4, 4, 3, 2)
+ real :: graph_expected(4, 4, 3), gabor_expected(4, 4, 3, 2)
+ real :: rank2_values(6, 2), rank2_expected(6, 2)
+ real(kind=8) :: generalized_values(4, 3, 4)
+ real(kind=8) :: generalized_expected(4, 3, 4)
+ real :: good_values(6, 2), good_expected(6, 2)
+ real :: strided_values(6, 2), strided_expected(6, 2)
+ real :: sequential_values(6, 2), sequential_expected(6, 2)
+ real :: constant_values(4, 2, 2), constant_expected(4, 2, 2)
+ integer :: indices(2), section_indices(2)
+ character(64) :: record
+
+ ! The facerec-shaped rank-3 and rank-4 accesses establish the baseline fast
+ ! addresses for trailing scalar dimensions and two independent descriptors.
+ graph = -1.0
+ gabor = -1.0
+ indices = [1, 3]
+ record = '1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16'
+ call fill_slices(graph, gabor, indices, 2_8, record)
+
+ if (any(graph(:, :, 1) /= reshape([1., 2., 3., 4.], [2, 2]))) &
+ error stop 1
+ if (any(graph(:, :, 2) /= -1.0)) error stop 2
+ if (any(graph(:, :, 3) /= reshape([5., 6., 7., 8.], [2, 2]))) &
+ error stop 3
+ if (any(gabor(:, :, 1, 2) /= reshape([9., 10., 11., 12.], [2, 2]))) &
+ error stop 4
+ if (any(gabor(:, :, 2, 2) /= -1.0)) error stop 5
+ if (any(gabor(:, :, 3, 2) /= &
+ reshape([13., 14., 15., 16.], [2, 2]))) error stop 6
+ if (any(gabor(:, :, :, 1) /= -1.0)) error stop 7
+
+ ! The first dimension remains contiguous while the retained outer dimension
+ ! has a nonstandard descriptor stride. This must use the byte fast path.
+ graph_storage = -1.0
+ gabor_storage = -1.0
+ record = '17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32'
+ call fill_slices(graph_storage(1:2, 1:4:2, :), &
+ gabor_storage(1:2, 1:4:2, :, :), indices, 2_8, record)
+ graph_expected = -1.0
+ graph_expected(1:2, 1:4:2, 1) = reshape([17., 18., 19., 20.], [2, 2])
+ graph_expected(1:2, 1:4:2, 3) = reshape([21., 22., 23., 24.], [2, 2])
+ gabor_expected = -1.0
+ gabor_expected(1:2, 1:4:2, 1, 2) = &
+ reshape([25., 26., 27., 28.], [2, 2])
+ gabor_expected(1:2, 1:4:2, 3, 2) = &
+ reshape([29., 30., 31., 32.], [2, 2])
+ if (any(graph_storage /= graph_expected)) error stop 8
+ if (any(gabor_storage /= gabor_expected)) error stop 9
+
+ ! A noncontiguous first dimension makes the runtime predicate false, so the
+ ! original sliced access must preserve the same semantics in the fallback.
+ graph_storage = -1.0
+ gabor_storage = -1.0
+ record = '33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48'
+ call fill_slices(graph_storage(1:4:2, 1:2, :), &
+ gabor_storage(1:4:2, 1:2, :, :), indices, 2_8, record)
+ graph_expected = -1.0
+ graph_expected(1:4:2, 1:2, 1) = reshape([33., 34., 35., 36.], [2, 2])
+ graph_expected(1:4:2, 1:2, 3) = reshape([37., 38., 39., 40.], [2, 2])
+ gabor_expected = -1.0
+ gabor_expected(1:4:2, 1:2, 1, 2) = &
+ reshape([41., 42., 43., 44.], [2, 2])
+ gabor_expected(1:4:2, 1:2, 3, 2) = &
+ reshape([45., 46., 47., 48.], [2, 2])
+ if (any(graph_storage /= graph_expected)) error stop 10
+ if (any(gabor_storage /= gabor_expected)) error stop 11
+
+ ! Non-one section lower bounds exercise the retained-section correction in
+ ! the byte fast path rather than relying only on structural FIR checks.
+ graph_storage = -1.0
+ gabor_storage = -1.0
+ record = '49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64'
+ call fill_offset_slices(graph_storage, gabor_storage, indices, 2_8, record)
+ graph_expected = -1.0
+ graph_expected(2:3, 2:3, 1) = reshape([49., 50., 51., 52.], [2, 2])
+ graph_expected(2:3, 2:3, 3) = reshape([53., 54., 55., 56.], [2, 2])
+ gabor_expected = -1.0
+ gabor_expected(2:3, 2:3, 1, 2) = &
+ reshape([57., 58., 59., 60.], [2, 2])
+ gabor_expected(2:3, 2:3, 3, 2) = &
+ reshape([61., 62., 63., 64.], [2, 2])
+ if (any(graph_storage /= graph_expected)) error stop 12
+ if (any(gabor_storage /= gabor_expected)) error stop 13
+
+ ! Vector subscripts keep each section on direct fir.array_coor operations.
+ ! Two supported owners of one descriptor use distinct dynamic lower bounds.
+ rank2_values = -1.0
+ rank2_expected = -1.0
+ section_indices = [1, 2]
+ call fill_rank2_patterns(rank2_values, 1, 4, section_indices, &
+ '65 66 67 68 69 70 71 72')
+ rank2_expected(1:2, 1:2) = reshape([65., 66., 67., 68.], [2, 2])
+ rank2_expected(4:5, 1:2) = reshape([69., 70., 71., 72.], [2, 2])
+ if (any(rank2_values /= rank2_expected)) error stop 14
+
+ ! Section/Section/Section validates the generalized retained-dimension
+ ! formula with eight-byte elements and non-one dynamic lower bounds. The
+ ! facerec procedures above cover trailing scalar coordinates separately.
+ generalized_values = -1.0_8
+ generalized_expected = -1.0_8
+ section_indices = [1, 3]
+ call fill_generalized(generalized_values, 2, 2, section_indices, &
+ '73 74 75 76 77 78 79 80')
+ generalized_expected(2:3, 1, 2) = [73.0_8, 74.0_8]
+ generalized_expected(2:3, 3, 2) = [75.0_8, 76.0_8]
+ generalized_expected(2:3, 1, 3) = [77.0_8, 78.0_8]
+ generalized_expected(2:3, 3, 3) = [79.0_8, 80.0_8]
+ if (any(generalized_values /= generalized_expected)) error stop 15
+
+ ! An unsupported dynamic-step descriptor must not disable an independent
+ ! supported descriptor or be rewritten as if its step were one.
+ good_values = -1.0
+ good_expected = -1.0
+ strided_values = -1.0
+ strided_expected = -1.0
+ section_indices = [1, 2]
+ call fill_isolated_descriptors(good_values, strided_values, &
+ section_indices, 2, &
+ '81 82 83 84 85 86', &
+ '87 88 89 90 91 92')
+ good_expected(2:4, 1:2) = reshape([81., 82., 83., 84., 85., 86.], [3, 2])
+ strided_expected(1:5:2, 1:2) = &
+ reshape([87., 88., 89., 90., 91., 92.], [3, 2])
+ if (any(good_values /= good_expected)) error stop 16
+ if (any(strided_values /= strided_expected)) error stop 17
+
+ ! One descriptor used by two sequential owners must retain the independently
+ ! frozen slice facts of both loops.
+ sequential_values = -1.0
+ sequential_expected = -1.0
+ call fill_sequential_owners(sequential_values, section_indices, &
+ '93 94 95 96', '97 98 99 100')
+ sequential_expected(1:2, 1:2) = reshape([93., 94., 95., 96.], [2, 2])
+ sequential_expected(4:5, 1:2) = reshape([97., 98., 99., 100.], [2, 2])
+ if (any(sequential_values /= sequential_expected)) error stop 18
+
+ ! A constant-one Scalar coordinate has a zero outer byte contribution. The
+ ! fast path must retain the correct element while folding the redundant
+ ! stride multiplication before later canonicalization.
+ constant_values = -1.0
+ constant_expected = -1.0
+ section_indices = [1, 2]
+ call fill_constant_scalar(constant_values, section_indices, &
+ '101 102 103 104')
+ constant_expected(2:3, 1:2, 1) = &
+ reshape([101.0, 102.0, 103.0, 104.0], [2, 2])
+ if (any(constant_values /= constant_expected)) error stop 19
+
+ ! A negative retained outer stride still satisfies the dimension-zero
+ ! predicate and must therefore produce the same byte-fast-path addresses as
+ ! the generic sliced access.
+ graph_storage = -1.0
+ gabor_storage = -1.0
+ record = '105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120'
+ call fill_slices(graph_storage(1:2, 4:2:-2, :), &
+ gabor_storage(1:2, 4:2:-2, :, :), indices, 2_8, record)
+ graph_expected = -1.0
+ graph_expected(1:2, 4:2:-2, 1) = &
+ reshape([105., 106., 107., 108.], [2, 2])
+ graph_expected(1:2, 4:2:-2, 3) = &
+ reshape([109., 110., 111., 112.], [2, 2])
+ gabor_expected = -1.0
+ gabor_expected(1:2, 4:2:-2, 1, 2) = &
+ reshape([113., 114., 115., 116.], [2, 2])
+ gabor_expected(1:2, 4:2:-2, 3, 2) = &
+ reshape([117., 118., 119., 120.], [2, 2])
+ if (any(graph_storage /= graph_expected)) error stop 20
+ if (any(gabor_storage /= gabor_expected)) error stop 21
+
+ ! CHECK: PASS
+ print '(A)', 'PASS'
+end program
+
+! Compile-time checks are paired with the executions above. They prove that
+! every supported runtime helper contains the guarded byte-address fast path;
+! the executable checks independently validate the addresses it computes.
+
+! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_slices(
+! VERSIONED: %[[GRAPH_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
+! VERSIONED-NEXT: fir.if %[[GRAPH_PRED]]
+! VERSIONED: %[[GRAPH_BYTE_BOX:.*]] = fir.convert %[[GRAPH:[^ ]+]]
+! VERSIONED-SAME: {{.*}}-> !fir.box<!fir.array<?xi8>>
+! VERSIONED: %[[GRAPH_BASE:.*]] = fir.box_addr %[[GRAPH_BYTE_BOX]]
+! VERSIONED: %[[GRAPH_ADDRESS:.*]] = fir.coordinate_of %[[GRAPH_BASE]],
+! VERSIONED-NEXT: %[[GRAPH_FAST:.*]] = fir.convert %[[GRAPH_ADDRESS]]
+! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[GRAPH_FAST]])
+! VERSIONED: %[[GABOR_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
+! VERSIONED-NEXT: fir.if %[[GABOR_PRED]]
+! VERSIONED: %[[GABOR_BYTE_BOX:.*]] = fir.convert %[[GABOR:[^ ]+]]
+! VERSIONED-SAME: {{.*}}-> !fir.box<!fir.array<?xi8>>
+! VERSIONED: %[[GABOR_BASE:.*]] = fir.box_addr %[[GABOR_BYTE_BOX]]
+! VERSIONED: %[[GABOR_ADDRESS:.*]] = fir.coordinate_of %[[GABOR_BASE]],
+! VERSIONED-NEXT: %[[GABOR_FAST:.*]] = fir.convert %[[GABOR_ADDRESS]]
+! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[GABOR_FAST]])
+
+! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_offset_slices(
+! VERSIONED: %[[OFFSET_GRAPH_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
+! VERSIONED-NEXT: fir.if %[[OFFSET_GRAPH_PRED]]
+! VERSIONED: %[[OFFSET_GRAPH_BYTE_BOX:.*]] = fir.convert %[[OFFSET_GRAPH:[^ ]+]]
+! VERSIONED: %[[OFFSET_GRAPH_BASE:.*]] = fir.box_addr %[[OFFSET_GRAPH_BYTE_BOX]]
+! VERSIONED: %[[OFFSET_GRAPH_ADDRESS:.*]] = fir.coordinate_of %[[OFFSET_GRAPH_BASE]],
+! VERSIONED-NEXT: %[[OFFSET_GRAPH_FAST:.*]] = fir.convert %[[OFFSET_GRAPH_ADDRESS]]
+! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[OFFSET_GRAPH_FAST]])
+! VERSIONED: %[[OFFSET_GABOR_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
+! VERSIONED-NEXT: fir.if %[[OFFSET_GABOR_PRED]]
+! VERSIONED: %[[OFFSET_GABOR_BYTE_BOX:.*]] = fir.convert %[[OFFSET_GABOR:[^ ]+]]
+! VERSIONED: %[[OFFSET_GABOR_BASE:.*]] = fir.box_addr %[[OFFSET_GABOR_BYTE_BOX]]
+! VERSIONED: %[[OFFSET_GABOR_ADDRESS:.*]] = fir.coordinate_of %[[OFFSET_GABOR_BASE]],
+! VERSIONED-NEXT: %[[OFFSET_GABOR_FAST:.*]] = fir.convert %[[OFFSET_GABOR_ADDRESS]]
+! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[OFFSET_GABOR_FAST]])
+
+! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_rank2_patterns(
+! VERSIONED: %[[RANK2_FIRST_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
+! VERSIONED-NEXT: fir.if %[[RANK2_FIRST_PRED]]
+! VERSIONED: %[[RANK2_FIRST_BYTE_BOX:.*]] = fir.convert %[[RANK2:[^ ]+]]
+! VERSIONED: %[[RANK2_FIRST_BASE:.*]] = fir.box_addr %[[RANK2_FIRST_BYTE_BOX]]
+! VERSIONED: %[[RANK2_FIRST_ADDRESS:.*]] = fir.coordinate_of %[[RANK2_FIRST_BASE]],
+! VERSIONED-NEXT: %[[RANK2_FIRST_FAST:.*]] = fir.convert %[[RANK2_FIRST_ADDRESS]]
+! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[RANK2_FIRST_FAST]])
+! VERSIONED: %[[RANK2_SECOND_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
+! VERSIONED-NEXT: fir.if %[[RANK2_SECOND_PRED]]
+! VERSIONED: %[[RANK2_SECOND_BYTE_BOX:.*]] = fir.convert %[[RANK2]]
+! VERSIONED: %[[RANK2_SECOND_BASE:.*]] = fir.box_addr %[[RANK2_SECOND_BYTE_BOX]]
+! VERSIONED: %[[RANK2_SECOND_ADDRESS:.*]] = fir.coordinate_of %[[RANK2_SECOND_BASE]],
+! VERSIONED-NEXT: %[[RANK2_SECOND_FAST:.*]] = fir.convert %[[RANK2_SECOND_ADDRESS]]
+! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[RANK2_SECOND_FAST]])
+
+! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_generalized(
+! VERSIONED: %[[GENERAL_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
+! VERSIONED-NEXT: fir.if %[[GENERAL_PRED]]
+! VERSIONED: %[[GENERAL_BYTE_BOX:.*]] = fir.convert %[[GENERAL:[^ ]+]]
+! VERSIONED: %[[GENERAL_BASE:.*]] = fir.box_addr %[[GENERAL_BYTE_BOX]]
+! VERSIONED: %[[GENERAL_ADDRESS:.*]] = fir.coordinate_of %[[GENERAL_BASE]],
+! VERSIONED-NEXT: %[[GENERAL_FAST:.*]] = fir.convert %[[GENERAL_ADDRESS]]
+! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[GENERAL_FAST]])
+
+! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_constant_scalar(
+! VERSIONED: %[[CONSTANT_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
+! VERSIONED-NEXT: fir.if %[[CONSTANT_PRED]]
+! VERSIONED: %[[CONSTANT_BYTE_BOX:.*]] = fir.convert %[[CONSTANT:[^ ]+]]
+! VERSIONED: %[[CONSTANT_BASE:.*]] = fir.box_addr %[[CONSTANT_BYTE_BOX]]
+! VERSIONED: %[[CONSTANT_ADDRESS:.*]] = fir.coordinate_of %[[CONSTANT_BASE]],
+! VERSIONED-NEXT: %[[CONSTANT_FAST:.*]] = fir.convert %[[CONSTANT_ADDRESS]]
+! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[CONSTANT_FAST]])
+
+! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_isolated_descriptors(
+! VERSIONED: %[[GOOD_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
+! VERSIONED-NEXT: fir.if %[[GOOD_PRED]]
+! VERSIONED: %[[GOOD_BYTE_BOX:.*]] = fir.convert %[[GOOD:[^ ]+]]
+! VERSIONED: %[[GOOD_BASE:.*]] = fir.box_addr %[[GOOD_BYTE_BOX]]
+! VERSIONED: %[[GOOD_ADDRESS:.*]] = fir.coordinate_of %[[GOOD_BASE]],
+! VERSIONED-NEXT: %[[GOOD_FAST:.*]] = fir.convert %[[GOOD_ADDRESS]]
+! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[GOOD_FAST]])
+
+! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_sequential_owners(
+! VERSIONED: %[[SEQUENTIAL_FIRST_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
+! VERSIONED-NEXT: fir.if %[[SEQUENTIAL_FIRST_PRED]]
+! VERSIONED: %[[SEQUENTIAL_FIRST_BYTE_BOX:.*]] = fir.convert %[[SEQUENTIAL:[^ ]+]]
+! VERSIONED: %[[SEQUENTIAL_FIRST_BASE:.*]] = fir.box_addr %[[SEQUENTIAL_FIRST_BYTE_BOX]]
+! VERSIONED: %[[SEQUENTIAL_FIRST_ADDRESS:.*]] = fir.coordinate_of %[[SEQUENTIAL_FIRST_BASE]],
+! VERSIONED-NEXT: %[[SEQUENTIAL_FIRST_FAST:.*]] = fir.convert %[[SEQUENTIAL_FIRST_ADDRESS]]
+! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[SEQUENTIAL_FIRST_FAST]])
+! VERSIONED: %[[SEQUENTIAL_SECOND_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
+! VERSIONED-NEXT: fir.if %[[SEQUENTIAL_SECOND_PRED]]
+! VERSIONED: %[[SEQUENTIAL_SECOND_BYTE_BOX:.*]] = fir.convert %[[SEQUENTIAL]]
+! VERSIONED: %[[SEQUENTIAL_SECOND_BASE:.*]] = fir.box_addr %[[SEQUENTIAL_SECOND_BYTE_BOX]]
+! VERSIONED: %[[SEQUENTIAL_SECOND_ADDRESS:.*]] = fir.coordinate_of %[[SEQUENTIAL_SECOND_BASE]],
+! VERSIONED-NEXT: %[[SEQUENTIAL_SECOND_FAST:.*]] = fir.convert %[[SEQUENTIAL_SECOND_ADDRESS]]
+! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[SEQUENTIAL_SECOND_FAST]])
diff --git a/flang-rt/test/lit.cfg.py b/flang-rt/test/lit.cfg.py
index 74958d8155ef6..091bcbdd9b941 100644
--- a/flang-rt/test/lit.cfg.py
+++ b/flang-rt/test/lit.cfg.py
@@ -101,6 +101,10 @@ def shjoin(args, sep=" "):
# Library path of libflang_rt.runtime.a/.so (for lib search path when using non-Flang driver for linking and LD_LIBRARY_PATH)
config.substitutions.append(("%libdir", config.flang_rt_output_resource_lib_dir))
+# Some driver tests require LLVM Flang-specific options.
+if getattr(config, "fortran_compiler_id", "") == "LLVMFlang":
+ config.available_features.add("llvm-flang")
+
# For CUDA offloading, additional steps (device linking) and libraries (cudart) are needed.
if config.flang_rt_experimental_offload_support == "CUDA":
config.available_features.add("offload-cuda")
diff --git a/flang-rt/test/lit.site.cfg.py.in b/flang-rt/test/lit.site.cfg.py.in
index 8e3c902d4a3a6..a2b402327f607 100644
--- a/flang-rt/test/lit.site.cfg.py.in
+++ b/flang-rt/test/lit.site.cfg.py.in
@@ -13,6 +13,7 @@ config.flang_rt_experimental_offload_support = "@FLANG_RT_EXPERIMENTAL_OFFLOAD_S
config.flang_rt_fortran_modules = @FLANG_RT_FORTRAN_MODULES@
config.cc = "@CMAKE_C_COMPILER@"
config.flang = "@CMAKE_Fortran_COMPILER@"
+config.fortran_compiler_id = "@CMAKE_Fortran_COMPILER_ID@"
config.osx_sysroot = path(r"@CMAKE_OSX_SYSROOT@")
config.target_triple = "@LLVM_TARGET_TRIPLE@"
diff --git a/flang/include/flang/Optimizer/Transforms/Passes.td b/flang/include/flang/Optimizer/Transforms/Passes.td
index 3b408c963f430..a792e5a012d44 100644
--- a/flang/include/flang/Optimizer/Transforms/Passes.td
+++ b/flang/include/flang/Optimizer/Transforms/Passes.td
@@ -448,6 +448,11 @@ def LoopVersioning : Pass<"loop-versioning", "mlir::func::FuncOp"> {
loops to be vectorized as well as other loop optimizations.
}];
let dependentDialects = [ "fir::FIROpsDialect", "mlir::DLTIDialect" ];
+ let options = [
+ Option<"enableSlices", "enable-slices", "bool",
+ /*default=*/"true",
+ "Enable versioning for supported static-unit array slices">
+ ];
}
def VScaleAttr : Pass<"vscale-attr", "mlir::func::FuncOp"> {
diff --git a/flang/lib/Optimizer/Transforms/CMakeLists.txt b/flang/lib/Optimizer/Transforms/CMakeLists.txt
index 9b25e590764ab..7d63951c63e74 100644
--- a/flang/lib/Optimizer/Transforms/CMakeLists.txt
+++ b/flang/lib/Optimizer/Transforms/CMakeLists.txt
@@ -82,6 +82,9 @@ add_flang_library(FIRTransforms
HLFIRDialect
MIFDialect
+ LINK_COMPONENTS
+ Core
+
MLIR_LIBS
MLIRAffineUtils
MLIRAnalysis
diff --git a/flang/lib/Optimizer/Transforms/LoopVersioning.cpp b/flang/lib/Optimizer/Transforms/LoopVersioning.cpp
index e983e75c67ddf..2a6b8fd2aec3e 100644
--- a/flang/lib/Optimizer/Transforms/LoopVersioning.cpp
+++ b/flang/lib/Optimizer/Transforms/LoopVersioning.cpp
@@ -15,10 +15,11 @@
/// that the elements are contiguous (stride == size of element), and one that
/// is the original generic loop.
///
-/// As a side-effect of the assumed element size stride, the array is also
-/// flattened to make it a 1D array - this is because the internal array
-/// structure must be either 1D or have known sizes in all dimensions - and at
-/// least one of the dimensions here is already unknown.
+/// As a side-effect of the assumed element size stride, slice-free accesses are
+/// flattened to make the array 1D. This is required because the internal array
+/// structure must be either 1D or have known sizes in all dimensions, and at
+/// least one dimension here is already unknown. Sliced accesses instead use a
+/// byte-address base and retain descriptor byte strides.
///
/// There are two distinct benefits here:
/// 1. The loop that iterates over the elements is somewhat simplified by the
@@ -54,14 +55,26 @@
#include "mlir/Dialect/DLTI/DLTI.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/IR/Dominance.h"
+#include "mlir/IR/IRMapping.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/TypeUtilities.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/DialectConversion.h"
#include "mlir/Transforms/RegionUtils.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SetVector.h"
+#include "llvm/IR/DataLayout.h"
#include "llvm/Support/Debug.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
+#include <algorithm>
+#include <cstdint>
+#include <limits>
+#include <memory>
+#include <optional>
+
namespace fir {
#define GEN_PASS_DEF_LOOPVERSIONING
#include "flang/Optimizer/Transforms/Passes.h.inc"
@@ -74,9 +87,74 @@ namespace {
class LoopVersioningPass
: public fir::impl::LoopVersioningBase<LoopVersioningPass> {
public:
+ /// Construct the pass with its TableGen defaults.
+ LoopVersioningPass() = default;
+ /// Construct the pass with programmatic option values.
+ LoopVersioningPass(fir::LoopVersioningOptions options) : Base(options) {}
void runOnOperation() override;
};
+/// Classifies one direct fir.slice triple using generic XArrayCoor semantics.
+enum class SliceTripleKind { Scalar, Section, Unsupported };
+
+/// Captures the lowering contract of one top-level FIR module.
+/// Nested builtin modules are rejected before this record is constructed, so
+/// one index width and kind mapping unambiguously govern later lowering.
+struct SliceTargetInfo {
+ unsigned indexWidth = 0;
+ /// Kind mapping owned by runOnOperation and valid throughout preflight.
+ const fir::KindMapping *kindMapping = nullptr;
+};
+
+/// Holds the effective source width and the low bits relevant to the 64-bit
+/// address domain. Limiting the retained APInt prevents pathological integer
+/// widths from multiplying cache memory while preserving every bit that can
+/// affect the final static-one classification. APInt is used even though at
+/// most 64 bits are retained: a single-word APInt stores the value inline and
+/// provides width-aware truncation and sign or zero extension without manual
+/// masks or special handling for width 64.
+struct StaticIntegerState {
+ unsigned width;
+ llvm::APInt bits;
+};
+
+/// Caches the constant state produced at every visited SSA value in a
+/// conversion chain. A missing key denotes an unvisited value, while a null
+/// optional records a value that cannot be evaluated. Analysis runs before
+/// modification, so every slice can share intermediate results without
+/// repeatedly walking common conversion-chain prefixes.
+using StaticIntegerCache =
+ llvm::DenseMap<mlir::Value, std::optional<StaticIntegerState>>;
+
+/// Caches the effective width of an MLIR type for one frozen target contract.
+/// Types are uniqued and the target remains immutable throughout preflight, so
+/// every coordinate, lower bound, and step of that type can share the result.
+using SliceWidthCache = llvm::DenseMap<mlir::Type, unsigned>;
+
+/// Immutable arithmetic decisions for one accepted source dimension.
+struct SliceDimensionFacts {
+ /// Whether the slice triple retains a section or selects one scalar.
+ SliceTripleKind kind;
+ /// Whether a retained section has no source-lower adjustment.
+ bool lowerIsOne;
+};
+
+/// Per-dimension arithmetic decisions shared by accesses using one fir.slice,
+/// stored in ascending source-dimension order.
+using SliceFacts = llvm::SmallVector<SliceDimensionFacts, 4>;
+
+/// Couples one exact sliced access with the facts frozen for its rewrite.
+/// Discovery fills source in deterministic IR order. Preflight fills facts
+/// only after validating that source, and emission uses both values directly.
+struct SliceAccessPlan {
+ /// Original operation whose clone is replaced in the fast path.
+ fir::ArrayCoorOp source;
+ /// Index of immutable slice facts, present only after successful preflight.
+ std::optional<unsigned> factsIndex;
+};
+
+struct UseNode;
+
/// @struct ArgInfo
/// A structure to hold an argument, the size of the argument and dimension
/// information.
@@ -85,11 +163,100 @@ struct ArgInfo {
size_t size;
unsigned rank;
fir::BoxDimsOp dims[CFI_MAX_RANK];
+ /// Element-size constant used by sliced byte-address emission.
+ mlir::Value elemSize;
+ /// Accepted direct-use node, or null for the existing slice-free path.
+ UseNode *sliceNode = nullptr;
+ /// Deterministic ordinal assigned at the first direct access.
+ size_t firstUseOrder = std::numeric_limits<size_t>::max();
};
+/// Stores the direct uses of one concrete descriptor for one nearest loop.
+/// Only nodes containing a slice outlive collection. Their operation handles
+/// form the deterministic worklist consumed by preflight and clone rewriting.
+struct UseNode {
+ /// Immediate loop containing every recorded access.
+ fir::DoLoopOp loop;
+ /// Descriptor and type information shared by the accesses.
+ ArgInfo info;
+ /// Sliced access plans in deterministic IR discovery order.
+ llvm::SmallVector<SliceAccessPlan, 2> accesses;
+ /// Facts in first-use order, shared by plans that use the same fir.slice.
+ llvm::SmallVector<SliceFacts, 2> sliceFacts;
+};
+
+/// Holds one provisional sliced descriptor while collecting a single loop.
+/// The wrapper owns the node until descriptor-wide preflight either retains
+/// or rejects it.
+struct SliceUse {
+ /// Sliced accesses and their shared descriptor information.
+ std::unique_ptr<UseNode> node;
+ /// Whether this sliced group must remain on the generic path.
+ bool rejected = false;
+};
+
+/// Owns the lazily created sliced-use tables for one loop callback.
+/// Slice-free loops never construct this state.
+struct LoopSliceUses {
+ /// Provisional descriptor groups in first sliced-access order.
+ llvm::SmallVector<SliceUse, 4> uses;
+ /// Maps a sliced descriptor to its provisional group.
+ llvm::DenseMap<mlir::Value, size_t> indices;
+};
+
+/// Groups retained sliced nodes of one concrete descriptor.
+/// Slice-free and rejected descriptors are tracked separately by one compact
+/// set, so they do not allocate owner vectors.
+struct DescriptorUses {
+ /// Stable nodes for owners containing at least one sliced access.
+ llvm::SmallVector<UseNode *, 2> sliced;
+ /// Whether two recorded owners have an ancestor-descendant relationship.
+ bool nested = false;
+};
+
+/// Owns all function-lifetime state used only by slice discovery.
+/// The optional owner in runOnOperation keeps this state absent when slice
+/// support is disabled and retains accepted nodes through transformation.
+struct SliceDiscovery {
+ /// Retained sliced owners grouped by concrete descriptor.
+ llvm::DenseMap<mlir::Value, DescriptorUses> descriptors;
+ /// Descriptors with a slice-free or locally rejected direct owner.
+ llvm::SmallDenseSet<mlir::Value, 4> rejected;
+ /// Stable storage for every viable sliced owner.
+ llvm::SmallVector<std::unique_ptr<UseNode>, 0> nodes;
+ /// Next deterministic first-sliced-access ordinal.
+ size_t nextUseOrder = 0;
+};
+
+/// Record one directly nested sliced access while collecting a loop.
+/// The returned index remains valid if later accesses grow sliceUses; a
+/// returned UseNode remains stable because it is heap allocated. The final
+/// result reports whether the descriptor was first seen in this owner. A
+/// rejected group returns no result and does not retain another access.
+static std::optional<std::tuple<size_t, UseNode *, bool>>
+recordSliceUse(fir::DoLoopOp loop, mlir::Value descriptor,
+ fir::ArrayCoorOp arrayCoor, ArgInfo &info,
+ LoopSliceUses &sliceUses, size_t &nextUseOrder) {
+ auto [foundUse, firstDirectUse] =
+ sliceUses.indices.try_emplace(descriptor, sliceUses.uses.size());
+ if (firstDirectUse) {
+ SliceUse &use = sliceUses.uses.emplace_back();
+ use.node = std::make_unique<UseNode>();
+ use.node->loop = loop;
+ use.node->info.firstUseOrder = nextUseOrder++;
+ }
+
+ SliceUse &use = sliceUses.uses[foundUse->second];
+ if (use.rejected)
+ return std::nullopt;
+ info.firstUseOrder = use.node->info.firstUseOrder;
+ use.node->accesses.push_back({arrayCoor, std::nullopt});
+ return std::tuple{foundUse->second, use.node.get(), firstDirectUse};
+}
+
/// @struct ArgsUsageInLoop
/// A structure providing information about the function arguments
-/// usage by the instructions immediately nested in a loop.
+/// usage by instructions whose nearest enclosing do_loop is the given loop.
struct ArgsUsageInLoop {
/// Mapping between the memref operand of an array indexing
/// operation (e.g. fir.coordinate_of) and the argument information.
@@ -110,10 +277,29 @@ struct ArgsUsageInLoop {
llvm::dbgs() << "Arguments usage info for loop:\n";
loop.print(llvm::dbgs(), printFlags);
llvm::dbgs() << "\nUsed args:\n";
- for (auto &use : usageInfo) {
- mlir::Value v = use.first;
- v.print(llvm::dbgs(), printFlags);
- llvm::dbgs() << "\n";
+ bool hasSlices = llvm::any_of(
+ usageInfo, [](const auto &use) { return use.second.sliceNode; });
+ if (hasSlices) {
+ assert(llvm::all_of(
+ usageInfo,
+ [](const auto &use) { return use.second.sliceNode; }) &&
+ "a sliced owner must contain only sliced descriptors");
+ llvm::SmallVector<const ArgInfo *, 4> args;
+ for (const auto &use : usageInfo)
+ args.push_back(&use.second);
+ llvm::sort(args, [](const ArgInfo *left, const ArgInfo *right) {
+ return left->firstUseOrder < right->firstUseOrder;
+ });
+ for (const ArgInfo *arg : args) {
+ arg->arg.print(llvm::dbgs(), printFlags);
+ llvm::dbgs() << "\n";
+ }
+ } else {
+ for (auto &use : usageInfo) {
+ mlir::Value v = use.first;
+ v.print(llvm::dbgs(), printFlags);
+ llvm::dbgs() << "\n";
+ }
}
llvm::dbgs() << "\nCannot transform args:\n";
for (mlir::Value arg : cannotTransform) {
@@ -141,6 +327,9 @@ struct ArgsUsageInLoop {
}
}
};
+
+/// Maps each discovered loop to its descriptor-use summary.
+using LoopUsageMap = llvm::DenseMap<fir::DoLoopOp, ArgsUsageInLoop>;
} // namespace
static fir::SequenceType getAsSequenceType(mlir::Value v) {
@@ -229,6 +418,449 @@ static mlir::Value normaliseVal(mlir::Value val) {
return unwrapPassThroughOps(unwrapReboxOp(val));
}
+/// Collect slice-specific state for one indexing operation during the existing
+/// loop walk. A true result means that an enabled sliced access was handled
+/// completely and must not enter the slice-free collection path. When slice
+/// support is disabled, the function preserves the existing rejection and
+/// lets that path perform its normal cleanup. A slice-free access may reject
+/// an earlier sliced use of the same descriptor but otherwise continues there.
+static bool collectSliceUse(fir::DoLoopOp loop, mlir::Operation *op,
+ ArgInfo &info, bool isOriginalArgument,
+ ArgsUsageInLoop &argsInLoop,
+ std::unique_ptr<LoopSliceUses> &loopUses,
+ std::optional<SliceDiscovery> &slices,
+ mlir::DominanceInfo &domInfo,
+ const fir::KindMapping &kindMap,
+ const mlir::DataLayout &dataLayout) {
+ auto arrayCoor = mlir::dyn_cast<fir::ArrayCoorOp>(op);
+ if (!arrayCoor || !arrayCoor.getSlice()) {
+ if (slices) {
+ // TODO: Support descriptors used by both sliced and slice-free accesses.
+ // Until then, this combination is intentionally unsupported.
+ // One slice-free direct owner makes the descriptor ineligible without
+ // retaining that owner's operations or ArgInfo.
+ slices->rejected.insert(info.arg);
+ if (loopUses)
+ if (auto found = loopUses->indices.find(info.arg);
+ found != loopUses->indices.end())
+ loopUses->uses[found->second].rejected = true;
+ }
+ return false;
+ }
+
+ if (!slices) {
+ argsInLoop.cannotTransform.insert(info.arg);
+ return false;
+ }
+
+ // A descriptor-wide decision cannot recover after any direct owner is
+ // rejected. Keep propagating the rejection without allocating access plans
+ // that preflight can never publish.
+ if (slices->rejected.contains(info.arg)) {
+ argsInLoop.cannotTransform.insert(info.arg);
+ argsInLoop.usageInfo.erase(info.arg);
+ return true;
+ }
+ if (!loopUses)
+ loopUses = std::make_unique<LoopSliceUses>();
+ auto recorded = recordSliceUse(loop, info.arg, arrayCoor, info, *loopUses,
+ slices->nextUseOrder);
+ if (!recorded) {
+ argsInLoop.cannotTransform.insert(info.arg);
+ argsInLoop.usageInfo.erase(info.arg);
+ return true;
+ }
+ auto [useIndex, node, firstUse] = *recorded;
+
+ // Dominance is owner-local, while rank and element size are invariant for
+ // the concrete descriptor. Reuse the initial argument facts or a retained
+ // owner's facts instead of repeating the layout query in every owner.
+ if (firstUse) {
+ if (!domInfo.dominates(info.arg, loop)) {
+ loopUses->uses[useIndex].rejected = true;
+ } else if (auto found = slices->descriptors.find(info.arg);
+ found != slices->descriptors.end() &&
+ !found->second.sliced.empty()) {
+ const ArgInfo &previous = found->second.sliced.front()->info;
+ info.rank = previous.rank;
+ info.size = previous.size;
+ } else if (!isOriginalArgument) {
+ std::tie(info.rank, info.size) =
+ getRankAndElementSize(kindMap, dataLayout, info.arg);
+ }
+ node->info = info;
+ if (info.rank == 0 || info.size == 0)
+ loopUses->uses[useIndex].rejected = true;
+ }
+
+ // Preserve the existing collection rejection until descriptor-wide
+ // preflight publishes the complete frozen plan.
+ argsInLoop.cannotTransform.insert(info.arg);
+ argsInLoop.usageInfo.erase(info.arg);
+ return true;
+}
+
+/// Return whether direct byte addressing would bypass descriptor semantics.
+static bool hasUnsupportedSliceSemantics(mlir::Value value,
+ mlir::func::FuncOp func) {
+ if (fir::isa_volatile_type(value.getType()))
+ return true;
+ mlir::Value root = value;
+ while (fir::ReboxOp rebox = root.getDefiningOp<fir::ReboxOp>()) {
+ if (!fir::reboxPreservesContinuity(rebox,
+ /*mayHaveNonDefaultLowerBounds=*/true,
+ /*checkWhole=*/false))
+ break;
+ if (rebox.getOptional() || fir::isa_volatile_type(rebox.getType()) ||
+ fir::isa_volatile_type(rebox.getBox().getType()))
+ return true;
+ root = rebox.getBox();
+ }
+ while (true) {
+ if (fir::DeclareOp declare = root.getDefiningOp<fir::DeclareOp>()) {
+ auto variable =
+ mlir::cast<fir::FortranVariableOpInterface>(declare.getOperation());
+ auto attrs = declare.getFortranAttrs();
+ if (variable.isOptional() || fir::isa_volatile_type(declare.getType()) ||
+ fir::isa_volatile_type(declare.getMemref().getType()) ||
+ (attrs &&
+ fir::bitEnumContainsAny(
+ *attrs, fir::FortranVariableFlagsEnum::fortran_volatile)))
+ return true;
+ root = declare.getMemref();
+ continue;
+ }
+ if (auto pack = root.getDefiningOp<fir::PackArrayOp>()) {
+ if (fir::isa_volatile_type(pack.getType()) ||
+ fir::isa_volatile_type(pack.getArray().getType()))
+ return true;
+ root = pack.getArray();
+ continue;
+ }
+ break;
+ }
+ if (auto blockArg = mlir::dyn_cast<mlir::BlockArgument>(root);
+ blockArg && blockArg.getOwner() == &func.getBody().front()) {
+ unsigned number = blockArg.getArgNumber();
+ return func.getArgAttr(number, fir::getOptionalAttrName()) ||
+ func.getArgAttr(number, fir::getVolatileAttrName());
+ }
+ return false;
+}
+
+/// Return whether a value is produced by fir.undefined.
+static bool isUndefined(mlir::Value value) {
+ return value && mlir::isa_and_nonnull<fir::UndefOp>(value.getDefiningOp());
+}
+
+/// Classify a slice triple exactly as generic XArrayCoor lowering does.
+static SliceTripleKind classifySliceTriple(mlir::Value lower, mlir::Value upper,
+ mlir::Value step) {
+ if (isUndefined(upper))
+ return SliceTripleKind::Scalar;
+ if (!isUndefined(lower) && !isUndefined(step))
+ return SliceTripleKind::Section;
+ return SliceTripleKind::Unsupported;
+}
+
+/// Derive the address contract of one top-level FIR module.
+/// A nested builtin module can be lowered either under its own contract or by
+/// an ancestor module pass, so this initial slice path rejects it fail closed.
+static std::optional<SliceTargetInfo>
+getSliceTargetInfo(mlir::ModuleOp module,
+ const fir::KindMapping &moduleKindMap) {
+ if (module->getParentOfType<mlir::ModuleOp>())
+ return std::nullopt;
+
+ auto getIndexWidth = [](mlir::ModuleOp owner) -> std::optional<unsigned> {
+ llvm::StringRef layoutString;
+ if (auto layout = owner->getAttrOfType<mlir::StringAttr>(
+ mlir::LLVM::LLVMDialect::getDataLayoutAttrName()))
+ layoutString = layout.getValue();
+ auto parsedLayout = llvm::DataLayout::parse(layoutString);
+ if (!parsedLayout) {
+ llvm::consumeError(parsedLayout.takeError());
+ return std::nullopt;
+ }
+ // FIR-to-LLVM lowers abstract MLIR index values to i32 only for 32-bit
+ // pointers and to i64 otherwise. Generic XArrayCoor computes boxed byte
+ // offsets in i64, but a 32-bit GEP observes the same low address bits.
+ return parsedLayout->getPointerSizeInBits(0) == 32 ? 32u : 64u;
+ };
+
+ std::optional<unsigned> indexWidth = getIndexWidth(module);
+ if (!indexWidth)
+ return std::nullopt;
+ return SliceTargetInfo{*indexWidth, &moduleKindMap};
+}
+
+/// Return the effective width of an integer-like slice operand. Admission
+/// checks and constant-chain simulation share this cached kind-mapped result.
+static unsigned getSliceOperandWidth(mlir::Type type,
+ const SliceTargetInfo &target,
+ SliceWidthCache &cache) {
+ if (auto found = cache.find(type); found != cache.end())
+ return found->second;
+
+ unsigned width = 0;
+ if (mlir::isa<mlir::IndexType>(type)) {
+ width = target.indexWidth;
+ } else if (auto integer = mlir::dyn_cast<mlir::IntegerType>(type)) {
+ width = integer.getWidth();
+ } else if (auto integer = mlir::dyn_cast<fir::IntegerType>(type)) {
+ assert(target.kindMapping && "slice target must retain its kind mapping");
+ width = target.kindMapping->getIntegerBitsize(integer.getFKind());
+ }
+ cache.try_emplace(type, width);
+ return width;
+}
+
+/// Return whether fir.convert can preserve this operand in the index domain.
+/// The caller supplies the already computed operand width so later
+/// classification can reuse it without another kind-mapping lookup.
+static bool canConvertSliceOperand(mlir::Value value, unsigned width,
+ unsigned indexWidth) {
+ mlir::Type type = value.getType();
+ // Generic XArrayCoor lowering sign-extends narrow integer adaptors, while
+ // fir.convert preserves builtin unsigned extension. An exact target-width
+ // unsigned value requires no extension, so both paths consume the same bits.
+ // Wider values remain excluded by the lossless width ceiling below.
+ if (auto integer = mlir::dyn_cast<mlir::IntegerType>(type);
+ integer && integer.isUnsigned() && width != indexWidth)
+ return false;
+ return width > 1 && width <= indexWidth;
+}
+
+/// Return whether integer widening from this source uses zero extension.
+static bool isZeroExtendedSliceInteger(mlir::Type type) {
+ auto integer = mlir::dyn_cast<mlir::IntegerType>(type);
+ return integer && (integer.isUnsigned() ||
+ (integer.isSignless() && integer.getWidth() == 1));
+}
+
+/// Evaluate one constant integer conversion chain for the module contract.
+/// Truncation, signed extension, builtin i1 extension, and FIR kind widths
+/// mirror ConvertOpConversion. Every intermediate result is retained so a
+/// later query can resume at the nearest previously evaluated predecessor.
+static std::optional<StaticIntegerState>
+evaluateStaticInteger(mlir::Value value, const SliceTargetInfo &target,
+ StaticIntegerCache &cache, SliceWidthCache &widthCache) {
+ if (auto found = cache.find(value); found != cache.end())
+ return found->second;
+
+ llvm::SmallVector<fir::ConvertOp, 4> conversions;
+ mlir::Value source = value;
+ while (!cache.contains(source)) {
+ auto convert = source.getDefiningOp<fir::ConvertOp>();
+ if (!convert)
+ break;
+ if (!fir::isa_integer(source.getType()) ||
+ !fir::isa_integer(convert.getValue().getType())) {
+ cache.try_emplace(source, std::nullopt);
+ break;
+ }
+ conversions.push_back(convert);
+ source = convert.getValue();
+ }
+
+ constexpr unsigned addressIndexWidth = 64;
+ std::optional<StaticIntegerState> state;
+ if (auto found = cache.find(source); found != cache.end()) {
+ state = found->second;
+ } else {
+ std::optional<llvm::APInt> constant = fir::getIntIfConstant(source);
+ unsigned sourceWidth =
+ getSliceOperandWidth(source.getType(), target, widthCache);
+ // Canonicalization may combine a conversion chain rooted at an i1 constant
+ // and materialize the direct conversion to index through signed getInt(),
+ // turning the set bit into -1. Reject the root so LoopVersioning has the
+ // same result whether canonicalization runs before or after this pass.
+ if (constant && sourceWidth > 1) {
+ unsigned retainedWidth = std::min(sourceWidth, addressIndexWidth);
+ llvm::APInt retained = isZeroExtendedSliceInteger(source.getType())
+ ? constant->zextOrTrunc(retainedWidth)
+ : constant->sextOrTrunc(retainedWidth);
+ state = StaticIntegerState{sourceWidth, std::move(retained)};
+ }
+ cache.try_emplace(source, state);
+ }
+
+ for (fir::ConvertOp convert : llvm::reverse(conversions)) {
+ if (state) {
+ mlir::Type fromType = convert.getValue().getType();
+ mlir::Type toType = convert.getType();
+ unsigned fromWidth = getSliceOperandWidth(fromType, target, widthCache);
+ unsigned toWidth = getSliceOperandWidth(toType, target, widthCache);
+ unsigned retainedFromWidth = std::min(fromWidth, addressIndexWidth);
+ if (!fromWidth || !toWidth || state->width != fromWidth ||
+ state->bits.getBitWidth() != retainedFromWidth) {
+ state.reset();
+ } else {
+ unsigned retainedToWidth = std::min(toWidth, addressIndexWidth);
+ llvm::APInt retained = state->bits;
+ if (retainedToWidth < retainedFromWidth)
+ retained = retained.trunc(retainedToWidth);
+ else if (retainedToWidth > retainedFromWidth)
+ retained = isZeroExtendedSliceInteger(fromType)
+ ? retained.zext(retainedToWidth)
+ : retained.sext(retainedToWidth);
+ state = StaticIntegerState{toWidth, std::move(retained)};
+ }
+ }
+ cache.try_emplace(convert.getResult(), state);
+ }
+ return state;
+}
+
+/// Return whether an integer value becomes positive one through fir.convert
+/// under the top-level module contract. Analysis runs before modification, so
+/// the result for the exact SSA value remains reusable throughout preflight.
+static bool isStaticOneInteger(mlir::Value value, const SliceTargetInfo &target,
+ StaticIntegerCache &cache,
+ SliceWidthCache &widthCache) {
+ std::optional<StaticIntegerState> state =
+ evaluateStaticInteger(value, target, cache, widthCache);
+ if (!state)
+ return false;
+ // Generic XArrayCoor uses a 64-bit address index and integerCast applies
+ // signed extension or truncation to the final step. The direct path does not
+ // materialize a proven unit step, so classify the value in that same domain.
+ constexpr unsigned addressIndexWidth = 64;
+ return state->bits.sextOrTrunc(addressIndexWidth).isOne();
+}
+
+/// Return whether a slice step uses a supported static-one form.
+/// A chain rooted at i1 is rejected so its classification is independent of
+/// canonicalization. An intermediate i1 produced from a wider integer is
+/// modeled with fir.convert's zero extension, while a final i1 remains
+/// unsupported because generic XArrayCoor lowering sign-extends it. Wider step
+/// types need no target-index admission check because a proven unit step is not
+/// materialized by the direct path.
+static bool isStaticOneSliceStep(mlir::Value value,
+ const SliceTargetInfo &target,
+ StaticIntegerCache &cache,
+ SliceWidthCache &widthCache) {
+ return getSliceOperandWidth(value.getType(), target, widthCache) > 1 &&
+ isStaticOneInteger(value, target, cache, widthCache);
+}
+
+/// Preflight descriptor properties shared by every one of its sliced accesses.
+/// The returned sequence type is reused by access-level preflight so rank,
+/// element size, and descriptor type are checked once per descriptor.
+static mlir::FailureOr<fir::SequenceType>
+analyzeSliceDescriptor(const ArgInfo &arg, unsigned indexWidth) {
+ auto reject =
+ [&](llvm::StringRef reason) -> mlir::FailureOr<fir::SequenceType> {
+ LLVM_DEBUG(llvm::dbgs()
+ << "Sliced array_coor rejected: " << reason << '\n');
+ return mlir::failure();
+ };
+
+ assert((indexWidth == 32 || indexWidth == 64) &&
+ "slice target must provide a supported index width");
+ uint64_t maxElementSize =
+ indexWidth == 32
+ ? static_cast<uint64_t>(std::numeric_limits<std::int32_t>::max())
+ : static_cast<uint64_t>(std::numeric_limits<std::int64_t>::max());
+ if (arg.rank > CFI_MAX_RANK || arg.size > maxElementSize)
+ return reject("UnsupportedRankOrElementSize");
+
+ auto boxType = mlir::dyn_cast<fir::BaseBoxType>(arg.arg.getType());
+ if (!boxType)
+ return reject("UnsupportedDescriptor");
+ // This initial slice path accepts only descriptors whose direct element is
+ // a sequence. Descriptors with heap or pointer storage wrappers remain on
+ // the generic path.
+ auto sequenceType = mlir::dyn_cast<fir::SequenceType>(boxType.getEleTy());
+ if (!sequenceType || sequenceType.getDimension() != arg.rank)
+ return reject("UnsupportedDescriptorElement");
+ return sequenceType;
+}
+
+/// Preflight properties that belong to one physical fir.array_coor access.
+/// The returned slice handle lets descriptor-wide preflight reuse slice-level
+/// classification without resolving the carrier a second time.
+static mlir::FailureOr<fir::SliceOp> validateSliceAccess(
+ fir::ArrayCoorOp op, const ArgInfo &arg, fir::SequenceType sequenceType,
+ const SliceTargetInfo &target, SliceWidthCache &widthCache) {
+ auto reject = [&](llvm::StringRef reason) -> mlir::FailureOr<fir::SliceOp> {
+ LLVM_DEBUG(llvm::dbgs()
+ << "Sliced array_coor rejected: " << reason << '\n');
+ return mlir::failure();
+ };
+
+ fir::SliceOp slice = op.getSlice().getDefiningOp<fir::SliceOp>();
+ if (!slice)
+ return reject("UnsupportedSliceCarrier");
+ if (fir::unwrapRefType(op.getType()) != sequenceType.getEleTy())
+ return reject("ResultElementTypeMismatch");
+ if (op.getIndices().size() != arg.rank)
+ return reject("UnsupportedIndexConvention");
+ if (mlir::Value shape = op.getShape()) {
+ auto shapeOp = shape.getDefiningOp<fir::ShapeOp>();
+ if (!shapeOp)
+ return reject("UnsupportedShapeCarrier");
+ }
+
+ for (mlir::Value index : op.getIndices()) {
+ unsigned width = getSliceOperandWidth(index.getType(), target, widthCache);
+ if (!canConvertSliceOperand(index, width, target.indexWidth))
+ return reject("UnsupportedIndexType");
+ }
+ return slice;
+}
+
+/// Preflight and decode one static-unit fir.slice without modifying the IR.
+/// A descriptor owner caches the result by operation so all accesses sharing
+/// this slice reuse the same classification and width proofs.
+static mlir::FailureOr<SliceFacts>
+analyzeSlice(fir::SliceOp slice, unsigned rank, const SliceTargetInfo &target,
+ StaticIntegerCache &cache, SliceWidthCache &widthCache) {
+ auto reject = [&](llvm::StringRef reason) -> mlir::FailureOr<SliceFacts> {
+ LLVM_DEBUG(llvm::dbgs()
+ << "Sliced array_coor rejected: " << reason << '\n');
+ return mlir::failure();
+ };
+
+ // TODO: Support the remaining valid fir.slice forms. This initial slice
+ // implementation intentionally leaves component paths, a leading scalar
+ // dimension, and non-unit section steps on the generic path.
+ if (!slice.getFields().empty())
+ return reject("UnsupportedComponentPath");
+ SliceFacts facts;
+ facts.reserve(rank);
+ mlir::ValueRange triples = slice.getTriples();
+ for (unsigned dim = 0; dim < rank; ++dim) {
+ mlir::Value lower = triples[3 * dim];
+ mlir::Value upper = triples[3 * dim + 1];
+ mlir::Value step = triples[3 * dim + 2];
+ SliceTripleKind kind = classifySliceTriple(lower, upper, step);
+ if (kind == SliceTripleKind::Unsupported)
+ return reject("UnsupportedSliceTriple");
+ if (dim == 0 && kind != SliceTripleKind::Section)
+ return reject("LeadingScalarDimension");
+
+ bool lowerIsOne = false;
+ if (kind == SliceTripleKind::Section) {
+ // A proven source lower one contributes no adjustment and is never
+ // materialized by the direct path, so its source width is irrelevant.
+ lowerIsOne = isStaticOneInteger(lower, target, cache, widthCache);
+ if (!lowerIsOne) {
+ unsigned lowerWidth =
+ getSliceOperandWidth(lower.getType(), target, widthCache);
+ if (!canConvertSliceOperand(lower, lowerWidth, target.indexWidth))
+ return reject("UnsupportedSectionLowerBound");
+ }
+ // Generic XArrayCoor sign-extends integer operands. The one-bit value
+ // `1` therefore denotes -1 after widening and is not a unit step.
+ if (!isStaticOneSliceStep(step, target, cache, widthCache))
+ return reject("NonUnitSectionStep");
+ }
+ facts.push_back({kind, lowerIsOne});
+ }
+ return facts;
+}
+
/// some FIR operations accept a fir.shape, a fir.shift or a fir.shapeshift.
/// fir.shift and fir.shapeshift allow us to extract lower bounds
/// if lowerbounds cannot be found, return nullptr
@@ -285,6 +917,458 @@ static mlir::Value getIndex(fir::FirOpBuilder &builder, mlir::Operation *op,
return mlir::arith::SubIOp::create(builder, coop.getLoc(), index, lb);
}
+/// Convert one cloned slice operand to the index type before arithmetic.
+/// Preflight proved that this conversion preserves the effective index-domain
+/// bit pattern used by generic lowering.
+static mlir::Value materializeIndex(fir::FirOpBuilder &builder,
+ mlir::Location loc, mlir::Value value) {
+ mlir::Type indexType = builder.getIndexType();
+ if (value.getType() == indexType)
+ return value;
+ return builder.createConvert(loc, indexType, value);
+}
+
+/// Extract a byte-address base for one accepted sliced descriptor.
+/// Descriptor order is controlled by the caller, and access-local arithmetic
+/// remains at each cloned indexing operation.
+static mlir::Value createSliceByteBase(fir::FirOpBuilder &builder,
+ mlir::Location loc, const ArgInfo &arg) {
+ fir::SequenceType::Shape shape{fir::SequenceType::getUnknownExtent()};
+ mlir::Type byteType = builder.getIntegerType(8);
+ mlir::Type byteArrayType = fir::SequenceType::get(shape, byteType);
+ mlir::Type byteBoxType = fir::BoxType::get(byteArrayType);
+ mlir::Value byteBox = builder.createConvert(loc, byteBoxType, arg.arg);
+ return fir::BoxAddrOp::create(builder, loc, builder.getRefType(byteArrayType),
+ byteBox);
+}
+
+/// Recreate one accepted sliced access in the descriptor byte domain.
+/// Every input-dependent check completed before modification, so this function
+/// only resolves clone-local operands and emits the frozen unit-step formula.
+static mlir::Value
+rewriteSliceAccess(fir::FirOpBuilder &builder, fir::ArrayCoorOp access,
+ fir::SliceOp slice, const SliceFacts &facts,
+ const ArgInfo &arg, mlir::Value byteBase, mlir::Value one) {
+ assert(facts.size() == arg.rank &&
+ "slice facts must describe every source dimension");
+ assert(arg.rank && one && one.getType() == builder.getIndexType() &&
+ "slice offset arithmetic requires a dominating index one");
+ builder.setInsertionPoint(access);
+ mlir::Location loc = access.getLoc();
+ mlir::Value totalBytes;
+ assert(slice && "an accepted access must retain its slice");
+ mlir::ValueRange triples = slice.getTriples();
+ assert(triples.size() == 3 * facts.size() &&
+ "slice triples must match the frozen source rank");
+
+ for (unsigned dim = 0; dim < facts.size(); ++dim) {
+ const SliceDimensionFacts &dimFacts = facts[dim];
+ mlir::Value index =
+ materializeIndex(builder, loc, access.getIndices()[dim]);
+ mlir::Value offset =
+ builder.createOrFold<mlir::arith::SubIOp>(loc, index, one);
+
+ // Generic XArrayCoor lowering adds (sliceLower - sourceLower) for a
+ // retained section. This patch accepts only implicit source lower one and
+ // statically unit section steps.
+ if (dimFacts.kind == SliceTripleKind::Section && !dimFacts.lowerIsOne) {
+ mlir::Value sliceLower = materializeIndex(builder, loc, triples[3 * dim]);
+ mlir::Value adjustment =
+ builder.createOrFold<mlir::arith::SubIOp>(loc, sliceLower, one);
+ offset =
+ builder.createOrFold<mlir::arith::AddIOp>(loc, offset, adjustment);
+ }
+
+ // The fast-edge predicate proves the dimension-zero byte stride equals
+ // the element size. Outer dimensions retain descriptor byte strides.
+ mlir::Value byteStride =
+ dim == 0 ? arg.elemSize : arg.dims[dim]->getResult(2);
+ // createOrFold removes a statically zero contribution immediately.
+ mlir::Value contribution =
+ builder.createOrFold<mlir::arith::MulIOp>(loc, byteStride, offset);
+ totalBytes = totalBytes ? builder.createOrFold<mlir::arith::AddIOp>(
+ loc, totalBytes, contribution)
+ : contribution;
+ }
+
+ assert(byteBase && "a sliced access requires a byte-address base");
+ assert(totalBytes && "a rewritten slice must produce a byte offset");
+ mlir::Type byteType = builder.getIntegerType(8);
+ mlir::Value byteAddress =
+ fir::CoordinateOp::create(builder, loc, builder.getRefType(byteType),
+ byteBase, mlir::ValueRange{totalBytes});
+ return builder.createConvert(loc, access.getType(), byteAddress);
+}
+
+/// Run complete descriptor-wide slice preflight before any IR modification.
+/// The function rejects nested or incomplete owner sets, preflights every
+/// physical sliced access, and freezes accepted plans for final owner selection
+/// after the existing slice-free cleanup has completed.
+/// Collection cannot accept uses incrementally because a later slice-free or
+/// unsupported access, or a nested owner, can invalidate every previously
+/// collected use of the descriptor. Running preflight after discovery preserves
+/// descriptor-wide atomicity without modifying and rolling back IR.
+static void freezeSliceOwnership(
+ llvm::ArrayRef<fir::DoLoopOp> originalLoops,
+ llvm::DenseMap<mlir::Value, DescriptorUses> &descriptorUses,
+ const llvm::SmallDenseSet<mlir::Value, 4> &rejectedDescriptors,
+ llvm::SmallVectorImpl<std::unique_ptr<UseNode>> &sliceNodes,
+ mlir::func::FuncOp func, mlir::ModuleOp module,
+ const fir::KindMapping &moduleKindMap) {
+ assert(!sliceNodes.empty() &&
+ "slice ownership requires at least one sliced use");
+
+ std::optional<SliceTargetInfo> target =
+ getSliceTargetInfo(module, moduleKindMap);
+ if (!target) {
+ LLVM_DEBUG(llvm::dbgs()
+ << "Sliced array_coor rejected: UnsupportedTargetLayout\n");
+ descriptorUses.clear();
+ sliceNodes.clear();
+ return;
+ }
+
+ llvm::SmallVector<DescriptorUses *, 4> multiOwnerDescriptors;
+ for (auto &[descriptor, uses] : descriptorUses) {
+ if (uses.sliced.size() > 1 && !rejectedDescriptors.contains(descriptor))
+ multiOwnerDescriptors.push_back(&uses);
+ }
+
+ if (!multiOwnerDescriptors.empty()) {
+ // Loop post-order defines compact subtree intervals. Build them only when
+ // a complete sliced descriptor has multiple owners and can therefore have
+ // an ancestor-descendant ownership conflict.
+ /// Inclusive range occupied by one loop subtree in loop post-order.
+ struct LoopInterval {
+ /// First post-order position belonging to the subtree.
+ size_t begin;
+ /// Post-order position of the loop itself.
+ size_t end;
+ };
+ llvm::DenseMap<fir::DoLoopOp, LoopInterval> intervals;
+ llvm::DenseMap<fir::DoLoopOp, size_t> subtreeBegins;
+ for (auto [index, loop] : llvm::enumerate(originalLoops)) {
+ size_t end = index;
+ size_t begin = end;
+ if (auto nested = subtreeBegins.find(loop); nested != subtreeBegins.end())
+ begin = nested->second;
+ intervals.try_emplace(loop, LoopInterval{begin, end});
+ if (fir::DoLoopOp parent = loop->getParentOfType<fir::DoLoopOp>()) {
+ auto [parentBegin, inserted] = subtreeBegins.try_emplace(parent, begin);
+ if (!inserted)
+ parentBegin->second = std::min(parentBegin->second, begin);
+ }
+ }
+
+ for (DescriptorUses *uses : multiOwnerDescriptors) {
+ std::optional<size_t> previousEnd;
+ for (UseNode *use : uses->sliced) {
+ fir::DoLoopOp owner = use->loop;
+ auto ownerInterval = intervals.find(owner);
+ if (ownerInterval == intervals.end()) {
+ uses->nested = true;
+ break;
+ }
+ LoopInterval interval = ownerInterval->second;
+ if (previousEnd && interval.begin <= *previousEnd) {
+ uses->nested = true;
+ break;
+ }
+ previousEnd = interval.end;
+ }
+ }
+ }
+
+ // Freeze only descriptors whose complete set of independent direct owners
+ // is made of supported slices. A descriptor is marked accepted only after
+ // every physical access of every owner has passed.
+ StaticIntegerCache staticIntegerCache;
+ SliceWidthCache widthCache;
+ // A direct fir.slice may dominate several independent owners. Cache both
+ // successful and rejected classifications so its immutable triples are
+ // decoded once for the whole function.
+ llvm::DenseMap<mlir::Operation *, std::optional<SliceFacts>> sliceFactsCache;
+ for (const std::unique_ptr<UseNode> &firstNode : sliceNodes) {
+ mlir::Value descriptor = firstNode->info.arg;
+ auto descriptorIt = descriptorUses.find(descriptor);
+ if (descriptorIt == descriptorUses.end() ||
+ descriptorIt->second.sliced.empty())
+ continue;
+ const DescriptorUses &descriptorInfo = descriptorIt->second;
+ if (firstNode.get() != descriptorInfo.sliced.front())
+ continue;
+ mlir::ArrayRef<UseNode *> uses = descriptorInfo.sliced;
+
+ // A slice-free or rejected owner makes the descriptor incomplete. A
+ // nested owner would require remapping facts after cloning the descendant.
+ // Both cases prevent publishing plans for the whole descriptor.
+ bool rejected =
+ descriptorInfo.nested || rejectedDescriptors.contains(descriptor);
+ if (rejected || hasUnsupportedSliceSemantics(descriptor, func))
+ continue;
+
+ // Run descriptor-level preflight once and reuse its verified sequence type
+ // for every physical access of every owner.
+ mlir::FailureOr<fir::SequenceType> sequenceType =
+ analyzeSliceDescriptor(firstNode->info, target->indexWidth);
+ if (mlir::failed(sequenceType))
+ continue;
+
+ // Preflight every owner and access before marking any owner accepted, so a
+ // late failure rejects the complete descriptor without partial plans.
+ for (UseNode *use : uses) {
+ // Store facts for each distinct fir.slice once per owner. Access plans
+ // retain stable indices rather than pointers into the growable vector.
+ llvm::DenseMap<mlir::Operation *, unsigned> factIndices;
+ for (SliceAccessPlan &plan : use->accesses) {
+ fir::ArrayCoorOp arrayCoor = plan.source;
+ assert(arrayCoor && arrayCoor.getSlice() &&
+ "collected slice access must remain attached");
+ // Replacing this operation would bypass its volatile access semantics.
+ if (fir::isa_volatile_type(arrayCoor.getType())) {
+ rejected = true;
+ break;
+ }
+ // Access-level preflight verifies the carrier, shape, indices, and
+ // result type and returns the concrete fir.slice for shared analysis.
+ mlir::FailureOr<fir::SliceOp> slice = validateSliceAccess(
+ arrayCoor, use->info, *sequenceType, *target, widthCache);
+ if (mlir::failed(slice)) {
+ rejected = true;
+ break;
+ }
+ auto [fact, firstUse] = factIndices.try_emplace(slice->getOperation(),
+ use->sliceFacts.size());
+ if (firstUse) {
+ // Reuse slice-level preflight across independent owners. The cache
+ // retains failures too, so an unsupported slice is never reanalyzed.
+ auto cached = sliceFactsCache.find(slice->getOperation());
+ if (cached == sliceFactsCache.end()) {
+ mlir::FailureOr<SliceFacts> facts =
+ analyzeSlice(*slice, use->info.rank, *target,
+ staticIntegerCache, widthCache);
+ cached = sliceFactsCache
+ .try_emplace(
+ slice->getOperation(),
+ mlir::failed(facts)
+ ? std::nullopt
+ : std::optional<SliceFacts>(std::move(*facts)))
+ .first;
+ }
+ if (!cached->second) {
+ rejected = true;
+ break;
+ }
+ // Copy immutable facts into owner-local storage whose lifetime covers
+ // later clone rewriting.
+ use->sliceFacts.push_back(*cached->second);
+ }
+ // Freeze the exact relation between this access and its owner-local
+ // facts without retaining a pointer that vector growth could
+ // invalidate.
+ plan.factsIndex = fact->second;
+ }
+ if (rejected)
+ break;
+ }
+
+ // Any owner or access failure rejects the complete descriptor plan.
+ if (rejected)
+ continue;
+
+ // Commit ownership atomically only after every access has complete facts.
+ for (UseNode *use : uses) {
+ assert(!use->accesses.empty() &&
+ llvm::all_of(use->accesses,
+ [](const SliceAccessPlan &plan) {
+ return plan.factsIndex.has_value();
+ }) &&
+ "accepted use must freeze every direct access");
+ use->info.sliceNode = use;
+ }
+ }
+
+ // Only accepted descriptor groups and nodes are needed after preflight.
+ // Moving unique_ptr values does not move the accepted pointees referenced by
+ // DescriptorUses.
+ for (auto &entry : descriptorUses) {
+ DescriptorUses &descriptorInfo = entry.second;
+ if (!descriptorInfo.sliced.empty() &&
+ !descriptorInfo.sliced.front()->info.sliceNode)
+ descriptorInfo.sliced.clear();
+ }
+ llvm::erase_if(sliceNodes, [](const std::unique_ptr<UseNode> &node) {
+ return !node->info.sliceNode;
+ });
+}
+
+/// Publish frozen slice plans after slice-free ownership is final.
+/// Publishing means adding fully preflighted plans to argsInLoops so the
+/// common loop-selection and rewriting phases can consume them.
+/// A descriptor is published for all of its independent owners only when none
+/// of those owners retains a slice-free rewrite. Iterating the frozen groups
+/// avoids another IR traversal and preserves descriptor-wide atomicity.
+static void publishSliceOwnership(
+ LoopUsageMap &argsInLoops,
+ llvm::DenseMap<mlir::Value, DescriptorUses> &descriptorUses,
+ llvm::SmallVectorImpl<std::unique_ptr<UseNode>> &sliceNodes) {
+ /// Complete owner records retained for one descriptor publication.
+ /// Pointers remain stable because publication modifies only the nested
+ /// usageInfo maps and never adds entries to argsInLoops.
+ struct Publication {
+ mlir::Value descriptor;
+ llvm::SmallVector<std::pair<UseNode *, ArgsUsageInLoop *>, 2> owners;
+ };
+
+ // Decide every group against the same pre-publication usageInfo snapshot.
+ // Retain the owner records found during that decision so publication neither
+ // repeats lookups nor admits a strict subset if a group is incomplete.
+ llvm::SmallVector<Publication, 4> publications;
+ for (auto &[descriptor, descriptorInfo] : descriptorUses) {
+ if (descriptorInfo.sliced.empty())
+ continue;
+ assert(llvm::all_of(
+ descriptorInfo.sliced,
+ [](const UseNode *use) { return use->info.sliceNode == use; }) &&
+ "published descriptor must retain complete frozen owners");
+
+ Publication publication{descriptor, {}};
+ publication.owners.reserve(descriptorInfo.sliced.size());
+ for (UseNode *use : descriptorInfo.sliced) {
+ auto loop = argsInLoops.find(use->loop);
+ // TODO: Support owners that combine sliced and slice-free descriptors.
+ // Until then, a missing owner record or any retained slice-free rewrite
+ // prevents publication of the complete sliced descriptor group.
+ if (loop == argsInLoops.end() || !loop->second.usageInfo.empty()) {
+ publication.owners.clear();
+ break;
+ }
+ publication.owners.emplace_back(use, &loop->second);
+ }
+ if (publication.owners.size() == descriptorInfo.sliced.size())
+ publications.push_back(std::move(publication));
+ }
+
+ // Publish only after every descriptor has observed the unchanged final
+ // slice-free state. Multiple sliced descriptors may then share one owner.
+ llvm::SmallDenseSet<UseNode *, 4> publishedNodes;
+ for (Publication &publication : publications) {
+ for (auto [use, owner] : publication.owners) {
+ auto [entry, inserted] =
+ owner->usageInfo.try_emplace(publication.descriptor, use->info);
+ (void)entry;
+ assert(inserted && "a frozen slice must have no slice-free duplicate");
+ (void)inserted;
+ // Rejection propagation is complete. Keep the final owner summary
+ // consistent with the successfully published descriptor.
+ owner->cannotTransform.remove(publication.descriptor);
+ publishedNodes.insert(use);
+ }
+ }
+
+ descriptorUses.clear();
+ llvm::erase_if(sliceNodes, [&](const std::unique_ptr<UseNode> &node) {
+ return !publishedNodes.contains(node.get());
+ });
+}
+
+#ifndef NDEBUG
+/// Assert that every frozen plan still names its original sliced use.
+/// This debug-only check catches accidental analysis/emission drift without
+/// adding release work or changing descriptor-local fail-closed decisions.
+static void validateFrozenAccesses(const UseNode &use) {
+ assert(use.loop && use.info.arg && !use.accesses.empty() &&
+ "accepted slice use must have an owner, descriptor, and accesses");
+ for (const SliceAccessPlan &plan : use.accesses) {
+ fir::ArrayCoorOp access = plan.source;
+ assert(access && access->getBlock() && access.getSlice() &&
+ "a planned sliced access must remain attached");
+ assert(access->getParentOfType<fir::DoLoopOp>() == use.loop &&
+ "a planned access must retain its immediate owner");
+ assert(access.getMemref() == use.info.arg &&
+ "a planned access must retain its descriptor");
+ assert(plan.factsIndex && *plan.factsIndex < use.sliceFacts.size() &&
+ use.sliceFacts[*plan.factsIndex].size() == use.info.rank &&
+ "a planned access must retain complete immutable facts");
+ }
+}
+#endif
+
+/// Rewrite every frozen sliced access in one cloned owner.
+/// Descriptor bases and physical accesses are consumed in their frozen order;
+/// one shared index constant dominates all access-local byte arithmetic.
+static bool rewriteSliceOwner(fir::FirOpBuilder &builder, mlir::Location loc,
+ mlir::IndexType indexType,
+ llvm::MutableArrayRef<ArgInfo> args,
+ mlir::IRMapping &cloneMap) {
+ assert(
+ llvm::all_of(
+ args, [](const ArgInfo &arg) { return arg.sliceNode != nullptr; }) &&
+ "a sliced owner must contain only sliced descriptors");
+
+ mlir::Value one = builder.createIntegerConstant(loc, indexType, 1);
+ bool changed = false;
+ for (ArgInfo &arg : args) {
+ // Only stride zero is needed by the branch predicate. Materialize the
+ // remaining descriptor metadata in the fast branch where it is consumed,
+ // so the fallback path does not perform unnecessary descriptor reads.
+ for (unsigned dim = 1; dim < arg.rank; ++dim) {
+ mlir::Value dimIndex = builder.createIntegerConstant(loc, indexType, dim);
+ arg.dims[dim] = fir::BoxDimsOp::create(builder, loc, indexType, indexType,
+ indexType, arg.arg, dimIndex);
+ }
+ mlir::Value byteBase = createSliceByteBase(builder, loc, arg);
+ auto insertionPoint = builder.saveInsertionPoint();
+ for (SliceAccessPlan &plan : arg.sliceNode->accesses) {
+ assert(plan.factsIndex &&
+ *plan.factsIndex < arg.sliceNode->sliceFacts.size() &&
+ "mapped access must retain its frozen facts");
+ mlir::Operation *mapped = cloneMap.lookup(plan.source.getOperation());
+ auto arrayCoor = mlir::cast<fir::ArrayCoorOp>(mapped);
+ assert(arrayCoor.getMemref() == arg.arg && arrayCoor.getSlice() &&
+ "mapped access must preserve its frozen plan");
+ fir::SliceOp slice = arrayCoor.getSlice().getDefiningOp<fir::SliceOp>();
+ assert(slice && "mapped access must retain its slice operation");
+ const SliceFacts &facts = arg.sliceNode->sliceFacts[*plan.factsIndex];
+ mlir::Value replacement = rewriteSliceAccess(builder, arrayCoor, slice,
+ facts, arg, byteBase, one);
+ arrayCoor.getResult().replaceAllUsesWith(replacement);
+ arrayCoor.erase();
+ if (slice->use_empty())
+ slice.erase();
+ changed = true;
+ }
+ builder.restoreInsertionPoint(insertionPoint);
+ }
+ return changed;
+}
+
+/// Materialize stride-zero metadata for a sliced owner and return its combined
+/// contiguity predicate. Metadata used only for address construction is
+/// deferred to the fast branch.
+static mlir::Value prepareSliceOwner(fir::FirOpBuilder &builder,
+ mlir::Location loc,
+ mlir::IndexType indexType,
+ llvm::MutableArrayRef<ArgInfo> args) {
+ mlir::Value condition;
+ for (ArgInfo &arg : args) {
+ assert(arg.sliceNode && "a sliced owner must retain frozen access facts");
+ mlir::Value dimIndex = builder.createIntegerConstant(loc, indexType, 0);
+ arg.dims[0] = fir::BoxDimsOp::create(builder, loc, indexType, indexType,
+ indexType, arg.arg, dimIndex);
+ // The sliced byte formula substitutes the element size for stride zero.
+ // This predicate proves that substitution for every access of descriptor.
+ arg.elemSize = builder.createIntegerConstant(loc, indexType, arg.size);
+ mlir::Value compare = mlir::arith::CmpIOp::create(
+ builder, loc, mlir::arith::CmpIPredicate::eq, arg.dims[0].getResult(2),
+ arg.elemSize);
+ condition = condition ? mlir::arith::AndIOp::create(builder, loc, compare,
+ condition)
+ : compare;
+ }
+ return condition;
+}
+
void LoopVersioningPass::runOnOperation() {
LLVM_DEBUG(llvm::dbgs() << "=== Begin " DEBUG_TYPE " ===\n");
mlir::func::FuncOp func = getOperation();
@@ -327,15 +1411,23 @@ void LoopVersioningPass::runOnOperation() {
mlir::SmallVector<fir::DoLoopOp> originalLoops;
// Information about the arguments usage by the instructions
// immediately nested in a loop.
- llvm::DenseMap<fir::DoLoopOp, ArgsUsageInLoop> argsInLoops;
+ LoopUsageMap argsInLoops;
+ // Keep slice state completely absent when the feature is disabled.
+ // Accepted nodes remain stable through transformation because ArgInfo
+ // stores pointers into this owner.
+ std::optional<SliceDiscovery> slices;
+ if (enableSlices)
+ slices.emplace();
auto &domInfo = getAnalysis<mlir::DominanceInfo>();
- // Traverse the loops in post-order and see
- // if those arguments are used inside any loop.
+ // Traverse the loops in post-order and group indexing operations by their
+ // nearest enclosing do_loop.
func.walk([&](fir::DoLoopOp loop) {
mlir::Block &body = *loop.getBody();
auto &argsInLoop = argsInLoops[loop];
+ // Allocate per-loop lookup state only after finding a sliced access.
+ std::unique_ptr<LoopSliceUses> loopSlices;
originalLoops.push_back(loop);
body.walk([&](mlir::Operation *op) {
// Support either fir.array_coor or fir.coordinate_of.
@@ -347,9 +1439,15 @@ void LoopVersioningPass::runOnOperation() {
mlir::Value operand = op->getOperand(0);
for (auto a : argsOfInterest) {
if (a.arg == normaliseVal(operand)) {
- // Use the reboxed value, not the block arg when re-creating the loop.
+ bool isOriginalArgument = a.arg == operand;
+ // Use the reboxed value, not the block arg when re-creating the
+ // loop.
a.arg = operand;
+ if (collectSliceUse(loop, op, a, isOriginalArgument, argsInLoop,
+ loopSlices, slices, domInfo, kindMap, *dl))
+ break;
+
// Check that the operand dominates the loop?
// If this is the case, record such operands in argsInLoop.cannot-
// Transform, so that they disable the transformation for the parent
@@ -357,20 +1455,13 @@ void LoopVersioningPass::runOnOperation() {
if (!domInfo.dominates(a.arg, loop))
argsInLoop.cannotTransform.insert(a.arg);
- // No support currently for sliced arrays.
- // This means that we cannot transform properly
- // instructions referencing a.arg in the whole loop
- // nest this loop is located in.
- if (auto arrayCoor = mlir::dyn_cast<fir::ArrayCoorOp>(op))
- if (arrayCoor.getSlice())
- argsInLoop.cannotTransform.insert(a.arg);
-
// We need to compute the rank and element size
// based on the operand, not the original argument,
// because array slicing may affect it.
std::tie(a.rank, a.size) = getRankAndElementSize(kindMap, *dl, a.arg);
- if (a.rank == 0 || a.size == 0)
+ if (a.rank == 0 || a.size == 0) {
argsInLoop.cannotTransform.insert(a.arg);
+ }
if (argsInLoop.cannotTransform.contains(a.arg)) {
// Remove any previously recorded usage, if any.
@@ -384,8 +1475,33 @@ void LoopVersioningPass::runOnOperation() {
}
}
});
+
+ // Move viable sliced nodes into function-lifetime storage. A locally
+ // rejected group is represented by one descriptor value only.
+ if (loopSlices)
+ for (SliceUse &use : loopSlices->uses) {
+ mlir::Value descriptor = use.node->info.arg;
+ if (use.rejected) {
+ slices->rejected.insert(descriptor);
+ continue;
+ }
+ slices->descriptors[descriptor].sliced.push_back(use.node.get());
+ slices->nodes.push_back(std::move(use.node));
+ }
});
+ if (slices) {
+ // Run complete slice preflight before any IR modification and freeze every
+ // accepted descriptor-wide rewrite plan.
+ if (!slices->nodes.empty())
+ freezeSliceOwnership(originalLoops, slices->descriptors, slices->rejected,
+ slices->nodes, func, module, kindMap);
+ // Rejection summaries are needed only while preflight is deciding groups.
+ slices->rejected.clear();
+ if (slices->nodes.empty())
+ slices.reset();
+ }
+
// Dump loops info after initial collection.
LLVM_DEBUG({
llvm::dbgs() << "Initial usage info:\n";
@@ -423,6 +1539,9 @@ void LoopVersioningPass::runOnOperation() {
});
}
+ if (slices)
+ publishSliceOwnership(argsInLoops, slices->descriptors, slices->nodes);
+
LLVM_DEBUG({
llvm::dbgs() << "Final usage info:\n";
for (fir::DoLoopOp loop : originalLoops) {
@@ -438,6 +1557,8 @@ void LoopVersioningPass::runOnOperation() {
struct OpsWithArgs {
mlir::Operation *op;
mlir::SmallVector<ArgInfo, 4> argsAndDims;
+ /// Whether this owner contains at least one frozen slice rewrite.
+ bool hasSlices = false;
};
mlir::SmallVector<OpsWithArgs, 4> loopsOfInterest;
for (fir::DoLoopOp loop : originalLoops) {
@@ -446,8 +1567,18 @@ void LoopVersioningPass::runOnOperation() {
continue;
OpsWithArgs info;
info.op = loop;
- for (auto &arg : argsInLoop.usageInfo)
+ for (auto &arg : argsInLoop.usageInfo) {
info.argsAndDims.push_back(arg.second);
+ info.hasSlices |= arg.second.sliceNode != nullptr;
+ }
+ // Pointer-keyed ordering is preserved for slice-free owners. A sliced
+ // owner uses stable first-access order for every descriptor guard and base
+ // emitted by the new path.
+ if (info.hasSlices)
+ llvm::sort(info.argsAndDims,
+ [](const ArgInfo &left, const ArgInfo &right) {
+ return left.firstUseOrder < right.firstUseOrder;
+ });
loopsOfInterest.emplace_back(std::move(info));
}
@@ -469,31 +1600,41 @@ void LoopVersioningPass::runOnOperation() {
<< "\n");
for (auto op : loopsOfInterest) {
LLVM_DEBUG(op.op->dump());
+#ifndef NDEBUG
+ if (op.hasSlices)
+ for (const ArgInfo &arg : op.argsAndDims)
+ if (arg.sliceNode)
+ validateFrozenAccesses(*arg.sliceNode);
+#endif
builder.setInsertionPoint(op.op);
mlir::Value allCompares = nullptr;
- // Ensure all of the arrays are unit-stride.
- for (auto &arg : op.argsAndDims) {
- // Fetch all the dimensions of the array, except the last dimension.
- // Always fetch the first dimension, however, so set ndims = 1 if
- // we have one dim
- unsigned ndims = arg.rank;
- for (unsigned i = 0; i < ndims; i++) {
- mlir::Value dimIdx = builder.createIntegerConstant(loc, idxTy, i);
- arg.dims[i] = fir::BoxDimsOp::create(builder, loc, idxTy, idxTy, idxTy,
- arg.arg, dimIdx);
- }
- // We only care about lowest order dimension, here.
- mlir::Value elemSize =
- builder.createIntegerConstant(loc, idxTy, arg.size);
- mlir::Value cmp = mlir::arith::CmpIOp::create(
- builder, loc, mlir::arith::CmpIPredicate::eq,
- arg.dims[0].getResult(2), elemSize);
- if (!allCompares) {
- allCompares = cmp;
- } else {
- allCompares =
- mlir::arith::AndIOp::create(builder, loc, cmp, allCompares);
+ if (op.hasSlices) {
+ allCompares = prepareSliceOwner(builder, loc, idxTy, op.argsAndDims);
+ } else {
+ // Ensure all of the arrays are unit-stride.
+ for (auto &arg : op.argsAndDims) {
+ // Fetch all the dimensions of the array, except the last dimension.
+ // Always fetch the first dimension, however, so set ndims = 1 if
+ // we have one dim
+ unsigned ndims = arg.rank;
+ for (unsigned i = 0; i < ndims; i++) {
+ mlir::Value dimIdx = builder.createIntegerConstant(loc, idxTy, i);
+ arg.dims[i] = fir::BoxDimsOp::create(builder, loc, idxTy, idxTy,
+ idxTy, arg.arg, dimIdx);
+ }
+ // We only care about lowest order dimension, here.
+ mlir::Value elemSize =
+ builder.createIntegerConstant(loc, idxTy, arg.size);
+ mlir::Value cmp = mlir::arith::CmpIOp::create(
+ builder, loc, mlir::arith::CmpIPredicate::eq,
+ arg.dims[0].getResult(2), elemSize);
+ if (!allCompares) {
+ allCompares = cmp;
+ } else {
+ allCompares =
+ mlir::arith::AndIOp::create(builder, loc, cmp, allCompares);
+ }
}
}
@@ -503,73 +1644,84 @@ void LoopVersioningPass::runOnOperation() {
builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
LLVM_DEBUG(llvm::dbgs() << "Creating cloned loop\n");
- mlir::Operation *clonedLoop = op.op->clone();
+ mlir::Operation *clonedLoop;
bool changed = false;
- for (auto &arg : op.argsAndDims) {
- fir::SequenceType::Shape newShape;
- newShape.push_back(fir::SequenceType::getUnknownExtent());
- auto elementType = fir::unwrapSeqOrBoxedSeqType(arg.arg.getType());
- mlir::Type arrTy = fir::SequenceType::get(newShape, elementType);
- mlir::Type boxArrTy = fir::BoxType::get(arrTy);
- mlir::Type refArrTy = builder.getRefType(arrTy);
- auto carg = fir::ConvertOp::create(builder, loc, boxArrTy, arg.arg);
- auto caddr = fir::BoxAddrOp::create(builder, loc, refArrTy, carg);
- auto insPt = builder.saveInsertionPoint();
- // Use caddr instead of arg.
- clonedLoop->walk([&](mlir::Operation *coop) {
- if (!mlir::isa<fir::CoordinateOp, fir::ArrayCoorOp>(coop))
- return;
- // Reduce the multi-dimensioned index to a single index.
- // This is required becase fir arrays do not support multiple dimensions
- // with unknown dimensions at compile time.
- // We then calculate the multidimensional array like this:
- // arr(x, y, z) bedcomes arr(z * stride(2) + y * stride(1) + x)
- // where stride is the distance between elements in the dimensions
- // 0, 1 and 2 or x, y and z.
- if (coop->getOperand(0) == arg.arg && coop->getOperands().size() >= 2) {
- builder.setInsertionPoint(coop);
- mlir::Value totalIndex;
- for (unsigned i = arg.rank - 1; i > 0; i--) {
- mlir::Value curIndex =
- builder.createConvert(loc, idxTy, getIndex(builder, coop, i));
- // Multiply by the stride of this array. Later we'll divide by the
- // element size.
- mlir::Value scale =
- builder.createConvert(loc, idxTy, arg.dims[i].getResult(2));
- curIndex =
- mlir::arith::MulIOp::create(builder, loc, scale, curIndex);
- totalIndex = (totalIndex) ? mlir::arith::AddIOp::create(
- builder, loc, curIndex, totalIndex)
- : curIndex;
- }
- // This is the lowest dimension - which doesn't need scaling
- mlir::Value finalIndex =
- builder.createConvert(loc, idxTy, getIndex(builder, coop, 0));
- if (totalIndex) {
- assert(llvm::isPowerOf2_32(arg.size) &&
- "Expected power of two here");
- unsigned bits = llvm::Log2_32(arg.size);
- mlir::Value elemShift =
- builder.createIntegerConstant(loc, idxTy, bits);
- totalIndex = mlir::arith::AddIOp::create(
- builder, loc,
- mlir::arith::ShRSIOp::create(builder, loc, totalIndex,
- elemShift),
- finalIndex);
- } else {
- totalIndex = finalIndex;
+ if (op.hasSlices) {
+ mlir::IRMapping cloneMap;
+ clonedLoop = op.op->clone(cloneMap);
+ changed =
+ rewriteSliceOwner(builder, loc, idxTy, op.argsAndDims, cloneMap);
+ } else {
+ clonedLoop = op.op->clone();
+ // Keep the existing slice-free emitter structurally unchanged.
+ for (auto &arg : op.argsAndDims) {
+ fir::SequenceType::Shape newShape;
+ newShape.push_back(fir::SequenceType::getUnknownExtent());
+ auto elementType = fir::unwrapSeqOrBoxedSeqType(arg.arg.getType());
+ mlir::Type arrTy = fir::SequenceType::get(newShape, elementType);
+ mlir::Type boxArrTy = fir::BoxType::get(arrTy);
+ mlir::Type refArrTy = builder.getRefType(arrTy);
+ auto carg = fir::ConvertOp::create(builder, loc, boxArrTy, arg.arg);
+ auto caddr = fir::BoxAddrOp::create(builder, loc, refArrTy, carg);
+ auto insPt = builder.saveInsertionPoint();
+ // Use caddr instead of arg.
+ clonedLoop->walk([&](mlir::Operation *coop) {
+ if (!mlir::isa<fir::CoordinateOp, fir::ArrayCoorOp>(coop))
+ return;
+ // Reduce the multi-dimensioned index to a single index.
+ // This is required becase fir arrays do not support multiple
+ // dimensions with unknown dimensions at compile time.
+ // We then calculate the multidimensional array like this:
+ // arr(x, y, z) bedcomes arr(z * stride(2) + y * stride(1) + x)
+ // where stride is the distance between elements in the dimensions
+ // 0, 1 and 2 or x, y and z.
+ if (coop->getOperand(0) == arg.arg &&
+ coop->getOperands().size() >= 2) {
+ builder.setInsertionPoint(coop);
+ mlir::Value totalIndex;
+ for (unsigned i = arg.rank - 1; i > 0; i--) {
+ mlir::Value curIndex =
+ builder.createConvert(loc, idxTy, getIndex(builder, coop, i));
+ // Multiply by the stride of this array. Later we'll divide by
+ // the element size.
+ mlir::Value scale =
+ builder.createConvert(loc, idxTy, arg.dims[i].getResult(2));
+ curIndex =
+ mlir::arith::MulIOp::create(builder, loc, scale, curIndex);
+ totalIndex = (totalIndex)
+ ? mlir::arith::AddIOp::create(
+ builder, loc, curIndex, totalIndex)
+ : curIndex;
+ }
+ // This is the lowest dimension - which doesn't need scaling
+ mlir::Value finalIndex =
+ builder.createConvert(loc, idxTy, getIndex(builder, coop, 0));
+ if (totalIndex) {
+ assert(llvm::isPowerOf2_32(arg.size) &&
+ "Expected power of two here");
+ unsigned bits = llvm::Log2_32(arg.size);
+ mlir::Value elemShift =
+ builder.createIntegerConstant(loc, idxTy, bits);
+ totalIndex = mlir::arith::AddIOp::create(
+ builder, loc,
+ mlir::arith::ShRSIOp::create(builder, loc, totalIndex,
+ elemShift),
+ finalIndex);
+ } else {
+ totalIndex = finalIndex;
+ }
+ auto newOp = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(elementType), caddr,
+ mlir::ValueRange{totalIndex});
+ LLVM_DEBUG(newOp->dump());
+ coop->getResult(0).replaceAllUsesWith(newOp->getResult(0));
+ coop->erase();
+ changed = true;
}
- auto newOp = fir::CoordinateOp::create(
- builder, loc, builder.getRefType(elementType), caddr,
- mlir::ValueRange{totalIndex});
- LLVM_DEBUG(newOp->dump());
- coop->getResult(0).replaceAllUsesWith(newOp->getResult(0));
- coop->erase();
- changed = true;
- }
- });
+ });
- builder.restoreInsertionPoint(insPt);
+ builder.restoreInsertionPoint(insPt);
+ }
}
assert(changed && "Expected operations to have changed");
diff --git a/flang/test/Fir/loop-versioning-slices-pointer32.fir b/flang/test/Fir/loop-versioning-slices-pointer32.fir
new file mode 100644
index 0000000000000..7613686a1401a
--- /dev/null
+++ b/flang/test/Fir/loop-versioning-slices-pointer32.fir
@@ -0,0 +1,173 @@
+// RUN: fir-opt --loop-versioning --cfg-conversion \
+// RUN: --cg-rewrite \
+// RUN: --fir-to-llvm-ir="target=i386-unknown-linux-gnu" \
+// RUN: --reconcile-unrealized-casts %s -o %t
+// RUN: FileCheck %s --input-file=%t --enable-var-scope
+// RUN: FileCheck %s --input-file=%t --check-prefix=P32-ARITH \
+// RUN: --enable-var-scope
+
+// Slices use the effective 32-bit address domain on a p32 target.
+// Narrow signed operands are extended before any offset arithmetic; the
+// generic fallback retains XArrayCoor's established i64 arithmetic.
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<>,
+ llvm.data_layout = "e-p:32:32:32:32"
+} {
+ func.func private @use(!fir.ref<i32>)
+
+ func.func @slice_pointer32(
+ %a: !fir.box<!fir.array<?x?xi32>>, %innerLower: i16,
+ %outerLower: ui32, %outer: i32) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %shape = fir.shape %c8, %c8 : (index, index) -> !fir.shape<2>
+ %slice = fir.slice %innerLower, %c8, %c1,
+ %outerLower, %c8, %c1
+ : (i16, index, index, ui32, index, index) -> !fir.slice<2>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a(%shape) [%slice] %i, %outer
+ : (!fir.box<!fir.array<?x?xi32>>, !fir.shape<2>, !fir.slice<2>,
+ index, i32) -> !fir.ref<i32>
+ fir.call @use(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+
+ // Every operand consumed by the p32 byte path must be representable without
+ // changing the generic XArrayCoor interpretation.
+ func.func @slice_pointer32_rejected_operands(
+ %wideIndexArg: !fir.box<!fir.array<?xi32>>,
+ %wideLowerArg: !fir.box<!fir.array<?xi32>>,
+ %unsignedLowerArg: !fir.box<!fir.array<?xi32>>,
+ %unsignedWideArg: !fir.box<!fir.array<?xi32>>,
+ %wideIndex: i64, %wideLower: i64, %unsignedLower: ui16,
+ %unsignedWide: ui64) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %unit = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ %wideLowerSlice = fir.slice %wideLower, %c8, %c1
+ : (i64, index, index) -> !fir.slice<1>
+ %unsignedLowerSlice = fir.slice %unsignedLower, %c8, %c1
+ : (ui16, index, index) -> !fir.slice<1>
+ %unsignedWideSlice = fir.slice %unsignedWide, %c8, %c1
+ : (ui64, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %wideIndexAddress = fir.array_coor %wideIndexArg [%unit] %wideIndex
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, i64)
+ -> !fir.ref<i32>
+ %wideLowerAddress =
+ fir.array_coor %wideLowerArg [%wideLowerSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %unsignedLowerAddress =
+ fir.array_coor %unsignedLowerArg [%unsignedLowerSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %unsignedWideAddress =
+ fir.array_coor %unsignedWideArg [%unsignedWideSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use(%wideIndexAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use(%wideLowerAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use(%unsignedLowerAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use(%unsignedWideAddress) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+
+ // A proven unit section lower is not materialized in the p32 fast path, so
+ // its wider source type does not restrict versioning.
+ func.func @slice_pointer32_static_wide_one(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %wideOne = arith.constant 1 : i64
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %wideOne, %c8, %c1
+ : (i64, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+
+}
+
+// CHECK-LABEL: llvm.func @slice_pointer32(
+// CHECK-SAME: %[[A:[^:]+]]: !llvm.ptr
+// CHECK-SAME: %[[INNER16:[^:]+]]: i16
+// CHECK-SAME: %[[OUTER_LOWER:[^:]+]]: i32
+// CHECK-SAME: %[[OUTER:[^:]+]]: i32
+// CHECK: %[[D0_INDEX:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK: %[[D0_STRIDE_PTR:.*]] = llvm.getelementptr %[[A]][0, 7, %[[D0_INDEX]], 2]
+// CHECK: %[[D0_STRIDE:.*]] = llvm.load %[[D0_STRIDE_PTR]] : !llvm.ptr -> i32
+// CHECK: %[[SIZE:.*]] = llvm.mlir.constant(4 : i32) : i32
+// CHECK: llvm.icmp "eq" %[[D0_STRIDE]], %[[SIZE]] : i32
+// CHECK: llvm.cond_br %{{.*}}, ^[[FAST:bb[0-9]+]], ^[[FALLBACK:bb[0-9]+]]
+// CHECK: ^[[FAST]]:
+// CHECK: %[[FAST_ONE:.*]] = llvm.mlir.constant(1 : i32) : i32
+// CHECK: %[[D1_INDEX:.*]] = llvm.mlir.constant(1 : i32) : i32
+// CHECK: %[[D1_STRIDE_PTR:.*]] = llvm.getelementptr %[[A]][0, 7, %[[D1_INDEX]], 2]
+// CHECK: %[[D1_STRIDE:.*]] = llvm.load %[[D1_STRIDE_PTR]] : !llvm.ptr -> i32
+// CHECK: %[[BASE_PTR:.*]] = llvm.getelementptr %[[A]][0, 0]
+// CHECK: %[[BYTE_BASE:.*]] = llvm.load %[[BASE_PTR]] : !llvm.ptr -> !llvm.ptr
+// CHECK: %[[INNER_INDEX_ADJUST:.*]] = llvm.sub {{.*}}, %[[FAST_ONE]] : i32
+// CHECK: %[[INNER32:.*]] = llvm.sext %[[INNER16]] : i16 to i32
+// CHECK: %[[INNER_LOWER_ADJUST:.*]] = llvm.sub %[[INNER32]], %[[FAST_ONE]] : i32
+// CHECK: %[[INNER_OFFSET:.*]] = llvm.add %[[INNER_INDEX_ADJUST]], %[[INNER_LOWER_ADJUST]] : i32
+// CHECK: %[[INNER_BYTES:.*]] = llvm.mul %[[INNER_OFFSET]], %[[SIZE]] : i32
+// CHECK: %[[OUTER_INDEX_ADJUST:.*]] = llvm.sub %[[OUTER]], %[[FAST_ONE]] : i32
+// CHECK: %[[OUTER_LOWER_ADJUST:.*]] = llvm.sub %[[OUTER_LOWER]], %[[FAST_ONE]] : i32
+// CHECK: %[[OUTER_OFFSET:.*]] = llvm.add %[[OUTER_INDEX_ADJUST]], %[[OUTER_LOWER_ADJUST]] : i32
+// CHECK: %[[OUTER_BYTES:.*]] = llvm.mul %[[D1_STRIDE]], %[[OUTER_OFFSET]] : i32
+// CHECK: %[[TOTAL:.*]] = llvm.add %[[INNER_BYTES]], %[[OUTER_BYTES]] : i32
+// CHECK: %[[FAST_ADDR:.*]] = llvm.getelementptr %[[BYTE_BASE]][%[[TOTAL]]] : (!llvm.ptr, i32) -> !llvm.ptr, i8
+// CHECK: llvm.call @use(%[[FAST_ADDR]])
+// CHECK: ^[[FALLBACK]]:
+// CHECK: llvm.sext %[[INNER16]] : i16 to i64
+// CHECK: llvm.sext %[[OUTER]] : i32 to i64
+// CHECK: llvm.sext %[[OUTER_LOWER]] : i32 to i64
+// CHECK: %[[FALLBACK_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use(%[[FALLBACK_ADDR]])
+
+// CHECK-LABEL: llvm.func @slice_pointer32_rejected_operands(
+// CHECK-SAME: %[[WIDE_INDEX:[^:]+]]: i64
+// CHECK-SAME: %[[WIDE_LOWER:[^:]+]]: i64
+// CHECK-SAME: %[[UNSIGNED_LOWER:[^:]+]]: i16
+// CHECK-SAME: %[[UNSIGNED_WIDE:[^:]+]]: i64
+// CHECK-COUNT-1: llvm.cond_br
+// CHECK-NOT: llvm.cond_br
+// CHECK-NOT: (!llvm.ptr, i32) -> !llvm.ptr, i8
+// CHECK: %[[WIDE_INDEX_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: %[[WIDE_LOWER_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: %[[UNSIGNED_LOWER_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: %[[UNSIGNED_WIDE_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use(%[[WIDE_INDEX_ADDR]])
+// CHECK: llvm.call @use(%[[WIDE_LOWER_ADDR]])
+// CHECK: llvm.call @use(%[[UNSIGNED_LOWER_ADDR]])
+// CHECK: llvm.call @use(%[[UNSIGNED_WIDE_ADDR]])
+// CHECK-NOT: llvm.cond_br
+
+// CHECK-LABEL: llvm.func @slice_pointer32_static_wide_one(
+// CHECK-SAME: %[[A:[^:]+]]: !llvm.ptr
+// CHECK: llvm.cond_br %{{.*}}, ^[[FAST:bb[0-9]+]], ^[[FALLBACK:bb[0-9]+]]
+// CHECK: ^[[FAST]]:
+// CHECK-NOT: llvm.trunc
+// CHECK: %[[FAST_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i32) -> !llvm.ptr, i8
+// CHECK: llvm.call @use(%[[FAST_ADDR]])
+// CHECK: ^[[FALLBACK]]:
+// CHECK: %[[FALLBACK_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use(%[[FALLBACK_ADDR]])
+
+// P32-ARITH-LABEL: llvm.func @slice_pointer32(
+// P32-ARITH: llvm.cond_br %{{.*}}, ^[[FAST:bb[0-9]+]], ^[[FALLBACK:bb[0-9]+]]
+// P32-ARITH: ^[[FAST]]:
+// P32-ARITH-NOT: llvm.ashr
+// P32-ARITH-NOT: llvm.lshr
+// P32-ARITH-NOT: llvm.shl
+// P32-ARITH-NOT: llvm.sdiv
+// P32-ARITH-NOT: llvm.udiv
+// P32-ARITH: ^[[FALLBACK]]:
diff --git a/flang/test/Fir/loop-versioning-slices-target-layout.fir b/flang/test/Fir/loop-versioning-slices-target-layout.fir
new file mode 100644
index 0000000000000..e50e4cfa3842b
--- /dev/null
+++ b/flang/test/Fir/loop-versioning-slices-target-layout.fir
@@ -0,0 +1,343 @@
+// RUN: fir-opt --loop-versioning --cfg-conversion \
+// RUN: --cg-rewrite \
+// RUN: --fir-to-llvm-ir="target=x86_64-unknown-linux-gnu" \
+// RUN: --reconcile-unrealized-casts --split-input-file %s | \
+// RUN: FileCheck %s --enable-var-scope
+// RUN: fir-opt --loop-versioning --split-input-file %s | \
+// RUN: FileCheck %s --check-prefix=FIR --enable-var-scope
+
+// f80:32 has a 12-byte allocated element size. The rank-two case combines it
+// with a runtime outer byte stride, yet slice rewriting stays in the
+// byte domain and needs neither a power-of-two size nor a quotient operation.
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<
+ #dlti.dl_entry<index, 64>,
+ #dlti.dl_entry<f80, dense<[32, 32]> : vector<2xi64>>
+ >,
+ fir.defaultkind = "a1c4d8i4l4r4",
+ fir.kindmap = "i2:1",
+ llvm.data_layout = "e-p:64:64-f80:32"
+} {
+ func.func private @use_f80(!fir.ref<f80>)
+ func.func private @use_i32(!fir.ref<i32>)
+
+ func.func @element_size_12(%a: !fir.class<!fir.array<?x?xf80>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1, %c1, %c8, %c1
+ : (index, index, index, index, index, index) -> !fir.slice<2>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ fir.do_loop %j = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i, %j
+ : (!fir.class<!fir.array<?x?xf80>>, !fir.slice<2>, index, index)
+ -> !fir.ref<f80>
+ fir.call @use_f80(%address) : (!fir.ref<f80>) -> ()
+ }
+ }
+ return
+ }
+
+ // One owner may version independent descriptors whose element sizes differ.
+ // Each descriptor must retain its own stride predicate and byte scale.
+ func.func @different_element_sizes(
+ %a: !fir.class<!fir.array<?xf80>>,
+ %b: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %aAddress = fir.array_coor %a [%slice] %i
+ : (!fir.class<!fir.array<?xf80>>, !fir.slice<1>, index)
+ -> !fir.ref<f80>
+ %bAddress = fir.array_coor %b [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_f80(%aAddress) : (!fir.ref<f80>) -> ()
+ fir.call @use_i32(%bAddress) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+
+ // An exact target-width unsigned section lower preserves all address bits,
+ // while a signless i64 coordinate observes the p64 index domain directly.
+ func.func @exact_width_unsigned_lower(
+ %a: !fir.box<!fir.array<?xi32>>, %sectionLower: ui64,
+ %coordinate: i64) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %sectionLower, %c8, %c1
+ : (ui64, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %coordinate
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, i64)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+
+ // A negative signed section lower is widened before the offset arithmetic
+ // and remains tied to the same fast and fallback consumers.
+ func.func @negative_section_lower(%a: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %cm3 = arith.constant -3 : i64
+ %slice = fir.slice %cm3, %c8, %c1
+ : (i64, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+
+ // The custom FIR integer kind is signed and lowers to i1. Its set bit must
+ // therefore be sign-extended to -1 rather than treated as a positive one.
+ func.func @kind_mapped_one_bit_widening(%value: i32) -> i64 {
+ %bit = fir.convert %value : (i32) -> !fir.int<2>
+ %wide = fir.convert %bit : (!fir.int<2>) -> i64
+ return %wide : i64
+ }
+}
+
+// CHECK-LABEL: llvm.func @element_size_12(
+// CHECK-SAME: %[[A:[^:]+]]: !llvm.ptr
+// CHECK: llvm.cond_br
+// CHECK: %[[D0:.*]] = llvm.mlir.constant(0 : i64) : i64
+// CHECK: %[[D0_STRIDE_ADDR:.*]] = llvm.getelementptr %[[A]][0, 7, %[[D0]], 2]
+// CHECK: %[[D0_STRIDE:.*]] = llvm.load %[[D0_STRIDE_ADDR]] : !llvm.ptr -> i64
+// CHECK: %[[SIZE:.*]] = llvm.mlir.constant(12 : i64) : i64
+// CHECK: llvm.icmp "eq" %[[D0_STRIDE]], %[[SIZE]] : i64
+// CHECK: llvm.cond_br %{{.*}}, ^[[FAST:bb[0-9]+]], ^[[FALLBACK:bb[0-9]+]]
+// CHECK: ^[[FAST]]:
+// CHECK-NOT: llvm.{{(sdiv|udiv|shl|ashr|lshr)}}
+// CHECK: %[[FAST_ONE:.*]] = llvm.mlir.constant(1 : i64) : i64
+// CHECK: %[[D1:.*]] = llvm.mlir.constant(1 : i64) : i64
+// CHECK: %[[D1_STRIDE_ADDR:.*]] = llvm.getelementptr %[[A]][0, 7, %[[D1]], 2]
+// CHECK: %[[D1_STRIDE:.*]] = llvm.load %[[D1_STRIDE_ADDR]] : !llvm.ptr -> i64
+// CHECK: %[[FAST_D0:.*]] = llvm.mul {{.*}}, %[[SIZE]] : i64
+// CHECK: %[[FAST_D1:.*]] = llvm.mul %[[D1_STRIDE]], {{.*}} : i64
+// CHECK: %[[FAST_BYTES:.*]] = llvm.add %[[FAST_D0]], %[[FAST_D1]] : i64
+// CHECK-NOT: llvm.{{(sdiv|udiv|shl|ashr|lshr)}}
+// CHECK: %[[FAST_ADDR:.*]] = llvm.getelementptr {{.*}}[%[[FAST_BYTES]]] : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK-NOT: llvm.{{(sdiv|udiv|shl|ashr|lshr)}}
+// CHECK: llvm.call @use_f80(%[[FAST_ADDR]])
+// CHECK-NOT: llvm.{{(sdiv|udiv|shl|ashr|lshr)}}
+// CHECK: ^[[FALLBACK]]:
+// CHECK: %[[FALLBACK_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use_f80(%[[FALLBACK_ADDR]])
+
+// CHECK-LABEL: llvm.func @different_element_sizes(
+// CHECK-SAME: %[[A:[^:]+]]: !llvm.ptr
+// CHECK-SAME: %[[B:[^:]+]]: !llvm.ptr
+// CHECK: %[[A_D0:.*]] = llvm.mlir.constant(0 : i64) : i64
+// CHECK: %[[A_STRIDE_ADDR:.*]] = llvm.getelementptr %[[A]][0, 7, %[[A_D0]], 2]
+// CHECK: %[[A_STRIDE:.*]] = llvm.load %[[A_STRIDE_ADDR]] : !llvm.ptr -> i64
+// CHECK: %[[A_SIZE:.*]] = llvm.mlir.constant(12 : i64) : i64
+// CHECK: %[[A_CONTIGUOUS:.*]] = llvm.icmp "eq" %[[A_STRIDE]], %[[A_SIZE]] : i64
+// CHECK: %[[B_D0:.*]] = llvm.mlir.constant(0 : i64) : i64
+// CHECK: %[[B_STRIDE_ADDR:.*]] = llvm.getelementptr %[[B]][0, 7, %[[B_D0]], 2]
+// CHECK: %[[B_STRIDE:.*]] = llvm.load %[[B_STRIDE_ADDR]] : !llvm.ptr -> i64
+// CHECK: %[[B_SIZE:.*]] = llvm.mlir.constant(4 : i64) : i64
+// CHECK: %[[B_CONTIGUOUS:.*]] = llvm.icmp "eq" %[[B_STRIDE]], %[[B_SIZE]] : i64
+// CHECK: %[[BOTH:.*]] = llvm.and %[[B_CONTIGUOUS]], %[[A_CONTIGUOUS]] : i1
+// CHECK: llvm.cond_br %[[BOTH]], ^[[FAST:bb[0-9]+]], ^[[FALLBACK:bb[0-9]+]]
+// CHECK: ^[[FAST]]:
+// CHECK: %[[A_BASE_ADDR:.*]] = llvm.getelementptr %[[A]][0, 0]
+// CHECK-NEXT: %[[A_BASE:.*]] = llvm.load %[[A_BASE_ADDR]] : !llvm.ptr -> !llvm.ptr
+// CHECK: %[[B_BASE_ADDR:.*]] = llvm.getelementptr %[[B]][0, 0]
+// CHECK-NEXT: %[[B_BASE:.*]] = llvm.load %[[B_BASE_ADDR]] : !llvm.ptr -> !llvm.ptr
+// CHECK: %[[A_OFFSET:.*]] = llvm.sub {{.*}} : i64
+// CHECK: %[[A_BYTES:.*]] = llvm.mul %[[A_OFFSET]], %[[A_SIZE]] : i64
+// CHECK: %[[A_FAST:.*]] = llvm.getelementptr %[[A_BASE]][%[[A_BYTES]]] : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: %[[B_OFFSET:.*]] = llvm.sub {{.*}} : i64
+// CHECK: %[[B_BYTES:.*]] = llvm.mul %[[B_OFFSET]], %[[B_SIZE]] : i64
+// CHECK: %[[B_FAST:.*]] = llvm.getelementptr %[[B_BASE]][%[[B_BYTES]]] : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use_f80(%[[A_FAST]])
+// CHECK: llvm.call @use_i32(%[[B_FAST]])
+// CHECK: ^[[FALLBACK]]:
+// CHECK: %[[A_FALLBACK:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: %[[B_FALLBACK:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use_f80(%[[A_FALLBACK]])
+// CHECK: llvm.call @use_i32(%[[B_FALLBACK]])
+
+// CHECK-LABEL: llvm.func @exact_width_unsigned_lower(
+// CHECK-SAME: %[[A:[^:]+]]: !llvm.ptr
+// CHECK-SAME: %[[LOWER:[^:]+]]: i64
+// CHECK-SAME: %[[COORDINATE:[^:]+]]: i64
+// CHECK: llvm.cond_br %{{.*}}, ^[[FAST:bb[0-9]+]], ^[[FALLBACK:bb[0-9]+]]
+// CHECK: ^[[FAST]]:
+// CHECK: %[[FAST_ONE:.*]] = llvm.mlir.constant(1 : i64) : i64
+// CHECK: %[[FAST_INDEX:.*]] = llvm.sub %[[COORDINATE]], %[[FAST_ONE]] : i64
+// CHECK: %[[FAST_LOWER:.*]] = llvm.sub %[[LOWER]], %[[FAST_ONE]] : i64
+// CHECK: llvm.add %[[FAST_INDEX]], %[[FAST_LOWER]] : i64
+// CHECK: %[[FAST_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use_i32(%[[FAST_ADDR]])
+// CHECK: ^[[FALLBACK]]:
+// CHECK: %[[FALLBACK_ONE:.*]] = llvm.mlir.constant(1 : i64) : i64
+// CHECK: llvm.sub %[[LOWER]], %[[FALLBACK_ONE]] overflow<nsw, nuw> : i64
+// CHECK: %[[FALLBACK_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use_i32(%[[FALLBACK_ADDR]])
+
+// CHECK-LABEL: llvm.func @negative_section_lower(
+// CHECK: %[[NEGATIVE:.*]] = llvm.mlir.constant(-3 : i64) : i64
+// CHECK: llvm.cond_br %{{.*}}, ^[[FAST:bb[0-9]+]], ^[[FALLBACK:bb[0-9]+]]
+// CHECK: ^[[FAST]]:
+// CHECK: %[[FAST_ONE:.*]] = llvm.mlir.constant(1 : i64) : i64
+// CHECK: %[[FAST_LOWER:.*]] = llvm.sub %[[NEGATIVE]], %[[FAST_ONE]] : i64
+// CHECK: %[[FAST_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use_i32(%[[FAST_ADDR]])
+// CHECK: ^[[FALLBACK]]:
+// CHECK: %[[FALLBACK_ONE:.*]] = llvm.mlir.constant(1 : i64) : i64
+// CHECK: llvm.sub %[[NEGATIVE]], %[[FALLBACK_ONE]] overflow<nsw, nuw> : i64
+// CHECK: %[[FALLBACK_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use_i32(%[[FALLBACK_ADDR]])
+
+// CHECK-LABEL: llvm.func @kind_mapped_one_bit_widening(
+// CHECK-SAME: %[[VALUE:[^:]+]]: i32
+// CHECK: %[[BIT:.*]] = llvm.trunc %[[VALUE]] : i32 to i1
+// CHECK: %[[WIDE:.*]] = llvm.sext %[[BIT]] : i1 to i64
+// CHECK: llvm.return %[[WIDE]] : i64
+
+// -----
+
+// FIR-to-LLVM uses i64 for abstract index on every non-p32 layout. Verify that
+// the byte-address path follows that contract even for a narrow pointer size.
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<>,
+ llvm.data_layout = "e-p:16:16"
+} {
+ func.func private @use_i32_p16(!fir.ref<i32>)
+
+ func.func @pointer16(%a: !fir.box<!fir.array<?xi32>>,
+ %sectionLower: i64, %coordinate: i64) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %sectionLower, %c8, %c1
+ : (i64, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %coordinate
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, i64)
+ -> !fir.ref<i32>
+ fir.call @use_i32_p16(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+}
+
+// CHECK-LABEL: llvm.func @pointer16(
+// CHECK: llvm.cond_br %{{.*}}, ^[[FAST:bb[0-9]+]], ^[[FALLBACK:bb[0-9]+]]
+// CHECK: ^[[FAST]]:
+// CHECK: %[[FAST_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use_i32_p16(%[[FAST_ADDR]])
+// CHECK: ^[[FALLBACK]]:
+// CHECK: %[[FALLBACK_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use_i32_p16(%[[FALLBACK_ADDR]])
+
+// -----
+
+// A pointer wider than 64 bits uses the same FIR-to-LLVM i64 index contract.
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<>,
+ llvm.data_layout = "e-p:128:128"
+} {
+ func.func private @use_i32_p128(!fir.ref<i32>)
+ func.func private @use_i32_p128_wide(!fir.ref<i32>)
+
+ func.func @pointer128(%a: !fir.box<!fir.array<?xi32>>,
+ %sectionLower: i64, %coordinate: i64) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %sectionLower, %c8, %c1
+ : (i64, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %coordinate
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, i64)
+ -> !fir.ref<i32>
+ fir.call @use_i32_p128(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+
+ // A non-step i128 operand exceeds the i64 FIR-to-LLVM address-index contract
+ // even when the target pointer itself is wider, so it must remain generic.
+ func.func @pointer128_wide_rejected(
+ %a: !fir.box<!fir.array<?xi32>>, %coordinate: i128) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %coordinate
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, i128)
+ -> !fir.ref<i32>
+ fir.call @use_i32_p128_wide(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+}
+
+// CHECK-LABEL: llvm.func @pointer128(
+// CHECK: llvm.cond_br %{{.*}}, ^[[FAST:bb[0-9]+]], ^[[FALLBACK:bb[0-9]+]]
+// CHECK: ^[[FAST]]:
+// CHECK: %[[FAST_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use_i32_p128(%[[FAST_ADDR]])
+// CHECK: ^[[FALLBACK]]:
+// CHECK: %[[FALLBACK_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use_i32_p128(%[[FALLBACK_ADDR]])
+
+// CHECK-LABEL: llvm.func @pointer128_wide_rejected(
+// CHECK-NOT: llvm.icmp "eq"
+// CHECK: llvm.getelementptr
+// CHECK-NOT: llvm.icmp "eq"
+// CHECK: llvm.call @use_i32_p128_wide
+// CHECK-NOT: llvm.icmp "eq"
+// CHECK: llvm.return
+
+// FIR-LABEL: func.func @pointer128_wide_rejected(
+// FIR-SAME: %[[A:[^:]+]]: !fir.box
+// FIR-NOT: fir.box_dims %[[A]],
+// FIR-NOT: fir.if
+// FIR: fir.do_loop
+// FIR: %[[ADDRESS:.*]] = fir.array_coor %[[A]]
+// FIR: fir.call @use_i32_p128_wide(%[[ADDRESS]])
+// FIR-NOT: fir.if
+// FIR: return
+
+// -----
+
+// A p64 layout may use a narrower native GEP index field. FIR-to-LLVM still
+// lowers abstract index arithmetic to i64 because the pointer itself is p64.
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<>,
+ llvm.data_layout = "e-p:64:64:64:32"
+} {
+ func.func private @use_i32_gep32(!fir.ref<i32>)
+
+ func.func @pointer64_gep32(%a: !fir.box<!fir.array<?xi32>>,
+ %sectionLower: i64, %coordinate: i64) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %sectionLower, %c8, %c1
+ : (i64, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %coordinate
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, i64)
+ -> !fir.ref<i32>
+ fir.call @use_i32_gep32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+}
+
+// CHECK-LABEL: llvm.func @pointer64_gep32(
+// CHECK: llvm.cond_br %{{.*}}, ^[[FAST:bb[0-9]+]], ^[[FALLBACK:bb[0-9]+]]
+// CHECK: ^[[FAST]]:
+// CHECK: %[[FAST_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use_i32_gep32(%[[FAST_ADDR]])
+// CHECK: ^[[FALLBACK]]:
+// CHECK: %[[FALLBACK_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
+// CHECK: llvm.call @use_i32_gep32(%[[FALLBACK_ADDR]])
diff --git a/flang/test/Transforms/loop-versioning-slices-disabled-nfc.fir b/flang/test/Transforms/loop-versioning-slices-disabled-nfc.fir
new file mode 100644
index 0000000000000..3c0d9b4fca6c2
--- /dev/null
+++ b/flang/test/Transforms/loop-versioning-slices-disabled-nfc.fir
@@ -0,0 +1,78 @@
+// RUN: fir-opt --loop-versioning %s -o %t.enabled
+// RUN: fir-opt '--loop-versioning=enable-slices=false' %s \
+// RUN: -o %t.disabled
+// RUN: diff %t.enabled %t.disabled
+// RUN: FileCheck %s --check-prefix=RESULT --input-file=%t.enabled \
+// RUN: --enable-var-scope
+
+// Compare emitted FIR byte-for-byte to prove that enabling slice support does
+// not change slice-free or rejected-descriptor transformation results.
+// Keep every function single-descriptor so the comparison does not depend on
+// pointer-keyed DenseMap iteration order between fir-opt processes.
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<>,
+ llvm.data_layout = "e-p:64:64:64:64"
+} {
+ func.func private @use_i32(!fir.ref<i32>)
+
+ // A slice-free candidate must retain the existing versioned form when slice
+ // support is enabled, since the feature is enabled by default.
+ func.func @slice_free(
+ %a: !fir.box<!fir.array<?x?xi32>>, %outer: index,
+ %initial: i32) -> i32 {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %result = fir.do_loop %i = %c1 to %c8 step %c1
+ iter_args(%sum = %initial) -> (i32) {
+ %address = fir.array_coor %a %i, %outer
+ : (!fir.box<!fir.array<?x?xi32>>, index, index) -> !fir.ref<i32>
+ %value = fir.load %address : !fir.ref<i32>
+ %next = arith.addi %sum, %value : i32
+ fir.result %next : i32
+ }
+ return %result : i32
+ }
+
+ // RESULT-LABEL: func.func @slice_free(
+ // RESULT: %[[IF:.*]] = fir.if
+ // RESULT: fir.coordinate_of
+ // RESULT: } else {
+ // RESULT: fir.array_coor
+ // RESULT: return %[[IF]] : i32
+
+ // A rejected inner slice must prevent rewriting the flat access to the same
+ // descriptor both with slice support enabled and explicitly disabled.
+ func.func @nested_rejected_slice_nfc(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c2
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %outer = fir.array_coor %a %i
+ : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+ fir.call @use_i32(%outer) : (!fir.ref<i32>) -> ()
+ fir.do_loop %j = %c1 to %c8 step %c1 {
+ %inner = fir.array_coor %a [%slice] %j
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%inner) : (!fir.ref<i32>) -> ()
+ }
+ }
+ return
+ }
+
+ // RESULT-LABEL: func.func @nested_rejected_slice_nfc(
+ // RESULT-SAME: %[[A:[^:]+]]: !fir.box
+ // RESULT: %[[SLICE:.*]] = fir.slice
+ // RESULT-NOT: fir.if
+ // RESULT: fir.do_loop
+ // RESULT: %[[OUTER:.*]] = fir.array_coor %[[A]]
+ // RESULT: fir.call @use_i32(%[[OUTER]])
+ // RESULT: fir.do_loop
+ // RESULT: %[[INNER:.*]] = fir.array_coor %[[A]]{{.*}}[%[[SLICE]]]
+ // RESULT: fir.call @use_i32(%[[INNER]])
+ // RESULT-NOT: fir.if
+ // RESULT: return
+}
diff --git a/flang/test/Transforms/loop-versioning-slices-enclosing-scope.fir b/flang/test/Transforms/loop-versioning-slices-enclosing-scope.fir
new file mode 100644
index 0000000000000..f97d27c91c098
--- /dev/null
+++ b/flang/test/Transforms/loop-versioning-slices-enclosing-scope.fir
@@ -0,0 +1,88 @@
+// RUN: fir-opt --split-input-file \
+// RUN: --pass-pipeline='builtin.module(func.func(loop-versioning),builtin.module(func.func(loop-versioning)),gpu.module(func.func(loop-versioning),builtin.module(func.func(loop-versioning))))' \
+// RUN: %s | FileCheck %s --enable-var-scope
+
+// A non-builtin module does not establish the FIR-to-LLVM index contract.
+// The nearest builtin module therefore supplies the p64 contract even when a
+// surrounding GPU scope carries an unrelated p32 layout attribute. The i64
+// lower bound makes that choice observable because a p32 contract rejects it.
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<>,
+ llvm.data_layout = "e-p:64:64:64:64"
+} {
+ func.func private @use_i32(!fir.ref<i32>)
+ gpu.module @scope attributes {
+ dlti.dl_spec = #dlti.dl_spec<>,
+ llvm.data_layout = "e-p:32:32:32:32"
+ } {
+ func.func @gpu_local_layout_is_not_an_owner(
+ %a: !fir.box<!fir.array<?xi32>>, %lower: i64) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %lower, %c8, %c1
+ : (i64, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ }
+}
+
+// CHECK-LABEL: func.func @gpu_local_layout_is_not_an_owner(
+// CHECK-SAME: %[[A:[^:]+]]: !fir.box
+// CHECK: fir.box_dims %[[A]],
+// CHECK: fir.if
+// CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+// CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+// CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+// CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+// CHECK: fir.coordinate_of %[[BYTES]],
+// CHECK-SAME: -> !fir.ref<i8>
+// CHECK: } else {
+// CHECK: fir.array_coor %[[A]]
+// CHECK: return
+
+// -----
+
+// A GPU scope does not make a nested builtin module unambiguous. The function
+// may still be lowered under the inner or outer builtin module contract, so
+// the initial slice path rejects it fail closed.
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<>,
+ llvm.data_layout = "e-p:64:64:64:64"
+} {
+ gpu.module @scope attributes {
+ dlti.dl_spec = #dlti.dl_spec<>,
+ llvm.data_layout = "e-p:32:32:32:32"
+ } {
+ builtin.module @case attributes {dlti.dl_spec = #dlti.dl_spec<>} {
+ func.func @nested_builtin_module_rejected(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ }
+ return
+ }
+ }
+ }
+}
+
+// CHECK-LABEL: func.func @nested_builtin_module_rejected(
+// CHECK-SAME: %[[A:[^:]+]]: !fir.box
+// CHECK: %[[SLICE:.*]] = fir.slice
+// CHECK-NOT: fir.box_dims %[[A]],
+// CHECK-NOT: fir.if
+// CHECK: fir.do_loop %[[I:[^ ]+]] =
+// CHECK: fir.array_coor %[[A]] [%[[SLICE]]] %[[I]]
+// CHECK-NOT: fir.if
+// CHECK: return
diff --git a/flang/test/Transforms/loop-versioning-slices-layout-conflict.fir b/flang/test/Transforms/loop-versioning-slices-layout-conflict.fir
new file mode 100644
index 0000000000000..787473270652c
--- /dev/null
+++ b/flang/test/Transforms/loop-versioning-slices-layout-conflict.fir
@@ -0,0 +1,92 @@
+// RUN: fir-opt \
+// RUN: --pass-pipeline='builtin.module(func.func(loop-versioning),builtin.module(func.func(loop-versioning)))' \
+// RUN: --split-input-file %s | FileCheck %s --enable-var-scope
+// RUN: fir-opt \
+// RUN: --pass-pipeline='builtin.module(func.func(loop-versioning),builtin.module(func.func(loop-versioning)),cfg-conversion,cg-rewrite,fir-to-llvm-ir,reconcile-unrealized-casts)' \
+// RUN: --split-input-file %s | FileCheck %s --check-prefix=LOWER \
+// RUN: --enable-var-scope
+
+// A function in a nested builtin module can be lowered under either the inner
+// or outer module contract. The initial slice path rejects this scope
+// even when both modules currently carry matching attributes.
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<>,
+ llvm.data_layout = "e-p:64:64:64:64"
+} {
+ module @inner attributes {
+ dlti.dl_spec = #dlti.dl_spec<>,
+ llvm.data_layout = "e-p:64:64:64:64"
+ } {
+ func.func @nested_module_rejected(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ }
+ return
+ }
+ }
+}
+
+// CHECK-LABEL: func.func @nested_module_rejected(
+// CHECK-SAME: %[[A:[^:]+]]: !fir.box
+// CHECK: %[[SLICE:.*]] = fir.slice
+// CHECK-NOT: fir.box_dims %[[A]],
+// CHECK-NOT: fir.if
+// CHECK: fir.do_loop %[[I:[^ ]+]] =
+// CHECK: fir.array_coor %[[A]] [%[[SLICE]]] %[[I]]
+// CHECK-NOT: fir.if
+// CHECK: return
+
+// LOWER-LABEL: llvm.func @nested_module_rejected(
+// LOWER-NOT: llvm.icmp "eq"
+// LOWER: llvm.getelementptr
+// LOWER: llvm.return
+
+// -----
+
+// A top-level FIR module without an explicit LLVM layout uses the same
+// default-64 contract as FIR-to-LLVM and remains eligible. The i64 lower bound
+// would be rejected if the missing layout were incorrectly treated as p32.
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<>
+} {
+ func.func @default_layout(
+ %a: !fir.box<!fir.array<?xi32>>, %lower: i64) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %lower, %c8, %c1
+ : (i64, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ }
+ return
+ }
+}
+
+// CHECK-LABEL: func.func @default_layout(
+// CHECK-SAME: %[[A:[^:]+]]: !fir.box
+// CHECK: fir.box_dims %[[A]],
+// CHECK: fir.if
+// CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+// CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+// CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+// CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+// CHECK: fir.coordinate_of %[[BYTES]],
+// CHECK-SAME: -> !fir.ref<i8>
+// CHECK: } else {
+// CHECK: fir.array_coor %[[A]]
+// CHECK: return
+
+// LOWER-LABEL: llvm.func @default_layout(
+// LOWER: llvm.icmp "eq"
+// LOWER: llvm.cond_br
+// LOWER: llvm.getelementptr
+// LOWER: llvm.return
diff --git a/flang/test/Transforms/loop-versioning-slices-source.f90 b/flang/test/Transforms/loop-versioning-slices-source.f90
new file mode 100644
index 0000000000000..fc57c9efb3521
--- /dev/null
+++ b/flang/test/Transforms/loop-versioning-slices-source.f90
@@ -0,0 +1,306 @@
+! RUN: %flang_fc1 -emit-fir -O3 %s -o - | \
+! RUN: fir-opt --verify-each --loop-versioning | \
+! RUN: FileCheck %s --enable-var-scope
+! RUN: %flang -S -O3 -fversion-loops-for-stride \
+! RUN: -mmlir --mlir-disable-threading \
+! RUN: -mmlir --mlir-print-ir-after=loop-versioning \
+! RUN: %s -o /dev/null 2>&1 | \
+! RUN: FileCheck %s --check-prefix=DRIVER --enable-var-scope
+! RUN: %flang -S -O3 -fversion-loops-for-stride \
+! RUN: -fdefault-integer-8 -fdefault-real-8 \
+! RUN: -mmlir --mlir-disable-threading \
+! RUN: -mmlir --mlir-print-ir-after=loop-versioning \
+! RUN: %s -o /dev/null 2>&1 | \
+! RUN: FileCheck %s --check-prefix=WIDE --enable-var-scope
+! RUN: %flang -S -O3 -fversion-loops-for-stride \
+! RUN: -frepack-arrays -frepack-arrays-contiguity=whole \
+! RUN: -mmlir --mlir-disable-threading \
+! RUN: -mmlir --mlir-print-ir-after=loop-versioning \
+! RUN: %s -o /dev/null 2>&1 | \
+! RUN: FileCheck %s --check-prefix=REPACK --enable-var-scope
+
+! Verify that source-expressible slice forms reach the byte-address fast path
+! through both the fc1 and driver pipelines. The additional driver modes make
+! element width and frontend-generated repacking observable.
+
+! Source-level rank-3 and rank-4 forms from the facerec expression. I/O keeps
+! the slices attached to fir.array_coor operations, so this test connects
+! frontend lowering to both the new byte fast path and the sliced fallback.
+subroutine facerec_slices(graph, gabor, indices, y)
+ implicit none
+ real, intent(inout) :: graph(:, :, :), gabor(:, :, :, :)
+ integer, intent(in) :: indices(2)
+ integer(kind=8), intent(in) :: y
+
+ read(*, *) graph(:, :, indices), gabor(:, :, indices, y)
+end subroutine
+
+! Keep the frontend wrapper configuration in a compiler test. The runtime test
+! separately verifies that values written through the repacked fast path are
+! copied back to a noncontiguous actual argument.
+subroutine repacked_slice(values, indices)
+ implicit none
+ real, intent(inout) :: values(:, :)
+ integer, intent(in) :: indices(2)
+
+ read(*, *) values(2:3, indices)
+end subroutine
+
+! Non-one section lower bounds require explicit retained-section corrections;
+! verify that the corresponding runtime scenario reaches the fast path.
+subroutine offset_slices(graph, gabor, indices, y)
+ implicit none
+ real, intent(inout) :: graph(:, :, :), gabor(:, :, :, :)
+ integer, intent(in) :: indices(2)
+ integer(kind=8), intent(in) :: y
+
+ read(*, *) graph(2:3, 2:3, indices), gabor(2:3, 2:3, indices, y)
+end subroutine
+
+! Two accesses to one descriptor use distinct dynamic lower bounds and must
+! receive independent address plans.
+subroutine rank2_patterns(values, lower0, lower1, indices)
+ implicit none
+ real, intent(inout) :: values(:, :)
+ integer, intent(in) :: lower0, lower1, indices(2)
+
+ read(*, *) values(lower0:lower0 + 1, indices), &
+ values(lower1:lower1 + 1, indices)
+end subroutine
+
+! Section/Scalar/Section with eight-byte elements verifies that acceptance is
+! not limited to prefix sections or the default real element size.
+subroutine generalized_slice(values, lower0, lower2, indices)
+ implicit none
+ real(kind=8), intent(inout) :: values(:, :, :)
+ integer, intent(in) :: lower0, lower2, indices(2)
+
+ read(*, *) values(lower0:lower0 + 1, indices, lower2:lower2 + 1)
+end subroutine
+
+! A constant-one trailing scalar has zero outer contribution; the fast address
+! must still select the same element as the sliced access.
+subroutine constant_scalar(values, indices)
+ implicit none
+ real, intent(inout) :: values(:, :, :)
+ integer, intent(in) :: indices(2)
+
+ read(*, *) values(2:3, indices, 1)
+end subroutine
+
+! An unsupported dynamic-step descriptor must remain generic without blocking
+! an independent descriptor whose section step is statically one.
+subroutine isolated_descriptors(good, strided, indices, step)
+ implicit none
+ real, intent(inout) :: good(:, :), strided(:, :)
+ integer, intent(in) :: indices(2), step
+
+ read(*, *) good(2:4, indices)
+ read(*, *) strided(1:5:step, indices)
+end subroutine
+
+! Sequential owners of one descriptor require separate frozen address facts so
+! each generated fast loop uses its own slice.
+subroutine sequential_owners(values, indices)
+ implicit none
+ real, intent(inout) :: values(:, :)
+ integer, intent(in) :: indices(2)
+
+ read(*, *) values(1:2, indices)
+ read(*, *) values(4:5, indices)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPfacerec_slices(
+! CHECK: %[[GRAPH_SLICE:.*]] = fir.slice
+! CHECK: fir.if
+! CHECK: %[[GRAPH_BOX:.*]] = fir.convert %[[GRAPH_DESC:[^ ]+]]
+! CHECK: %[[GRAPH_BYTES:.*]] = fir.box_addr %[[GRAPH_BOX]]
+! CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+! CHECK: %[[GRAPH_ADDRESS:.*]] = fir.coordinate_of %[[GRAPH_BYTES]],
+! CHECK: %[[GRAPH_FAST:.*]] = fir.convert %[[GRAPH_ADDRESS]]
+! CHECK-SAME: -> !fir.ref<f32>
+! CHECK: fir.call @_FortranAioInputReal32({{.*}}, %[[GRAPH_FAST]])
+! CHECK: } else {
+! CHECK: %[[GRAPH_FALLBACK:.*]] = fir.array_coor %[[GRAPH_DESC]]{{.*}}[%[[GRAPH_SLICE]]]
+! CHECK: fir.call @_FortranAioInputReal32({{.*}}, %[[GRAPH_FALLBACK]])
+! CHECK: %[[GABOR_SLICE:.*]] = fir.slice
+! CHECK: fir.if
+! CHECK: %[[GABOR_BOX:.*]] = fir.convert %[[GABOR_DESC:[^ ]+]]
+! CHECK: %[[GABOR_BYTES:.*]] = fir.box_addr %[[GABOR_BOX]]
+! CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+! CHECK: %[[GABOR_ADDRESS:.*]] = fir.coordinate_of %[[GABOR_BYTES]],
+! CHECK: %[[GABOR_FAST:.*]] = fir.convert %[[GABOR_ADDRESS]]
+! CHECK-SAME: -> !fir.ref<f32>
+! CHECK: fir.call @_FortranAioInputReal32({{.*}}, %[[GABOR_FAST]])
+! CHECK: } else {
+! CHECK: %[[GABOR_FALLBACK:.*]] = fir.array_coor %[[GABOR_DESC]]{{.*}}[%[[GABOR_SLICE]]]
+! CHECK: fir.call @_FortranAioInputReal32({{.*}}, %[[GABOR_FALLBACK]])
+
+! DRIVER: IR Dump After LoopVersioning
+! DRIVER-LABEL: func.func @_QPfacerec_slices(
+! DRIVER: fir.if
+! DRIVER: !fir.ref<!fir.array<?xi8>>
+! DRIVER: fir.coordinate_of
+! DRIVER: } else {
+! DRIVER: fir.array_coor
+! DRIVER-LABEL: func.func @_QPrepacked_slice(
+
+! WIDE-LABEL: func.func @_QPfacerec_slices(
+! WIDE-SAME: !fir.box<!fir.array<?x?x?xf64>>
+! WIDE: %[[WIDE_GRAPH:.*]] = fir.declare {{.*}}uniq_name = "_QFfacerec_slicesEgraph"
+! WIDE: %[[WIDE_GRAPH_REBOX:.*]] = fir.rebox %[[WIDE_GRAPH]]
+! WIDE: fir.box_dims %[[WIDE_GRAPH_REBOX]],
+! WIDE: %[[WIDE_D0:.*]]:3 = fir.box_dims %[[WIDE_GRAPH]],
+! WIDE: %[[WIDE_SIZE:.*]] = arith.constant 8 : index
+! WIDE: %[[WIDE_PRED:.*]] = arith.cmpi eq, %[[WIDE_D0]]#2, %[[WIDE_SIZE]] : index
+! WIDE: fir.if %[[WIDE_PRED]]
+! WIDE: %[[WIDE_ADDRESS:.*]] = fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! WIDE-NEXT: %[[WIDE_FAST:.*]] = fir.convert %[[WIDE_ADDRESS]] : (!fir.ref<i8>) -> !fir.ref<f64>
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[WIDE_FAST]])
+! WIDE: } else {
+! WIDE: %[[WIDE_FALLBACK:.*]] = fir.array_coor
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[WIDE_FALLBACK]])
+! WIDE-LABEL: func.func @_QPrepacked_slice(
+
+! REPACK-LABEL: func.func @_QPrepacked_slice(
+! REPACK: %[[PACKED:.*]] = fir.pack_array
+! REPACK: %[[DECLARED:.*]] = fir.declare %[[PACKED]]
+! REPACK: fir.if
+! REPACK: %[[BYTE_BOX:.*]] = fir.convert %[[DECLARED]]
+! REPACK-SAME: -> !fir.box<!fir.array<?xi8>>
+! REPACK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+! REPACK: %[[BYTE_ADDRESS:.*]] = fir.coordinate_of %[[BYTES]], {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! REPACK-NEXT: %[[FAST:.*]] = fir.convert %[[BYTE_ADDRESS]] : (!fir.ref<i8>) -> !fir.ref<f32>
+! REPACK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[FAST]])
+! REPACK: } else {
+! REPACK: %[[FALLBACK:.*]] = fir.array_coor %[[DECLARED]]
+! REPACK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[FALLBACK]])
+! REPACK-LABEL: func.func @_QPoffset_slices(
+
+! CHECK-LABEL: func.func @_QPoffset_slices(
+! CHECK: %[[OFFSET_GRAPH_SLICE:.*]] = fir.slice
+! CHECK: fir.if
+! CHECK: %[[OFFSET_GRAPH_BOX:.*]] = fir.convert %[[OFFSET_GRAPH_DESC:[^ ]+]]
+! CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+! CHECK: %[[OFFSET_GRAPH_BYTES:.*]] = fir.box_addr %[[OFFSET_GRAPH_BOX]]
+! CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+! CHECK: %[[OFFSET_GRAPH_ADDRESS:.*]] = fir.coordinate_of %[[OFFSET_GRAPH_BYTES]],
+! CHECK-SAME: -> !fir.ref<i8>
+! CHECK-NEXT: %[[OFFSET_GRAPH_FAST:.*]] = fir.convert %[[OFFSET_GRAPH_ADDRESS]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[OFFSET_GRAPH_FAST]])
+! CHECK: } else {
+! CHECK: %[[OFFSET_GRAPH_FALLBACK:.*]] = fir.array_coor %[[OFFSET_GRAPH_DESC]]{{.*}}[%[[OFFSET_GRAPH_SLICE]]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[OFFSET_GRAPH_FALLBACK]])
+! CHECK: %[[OFFSET_GABOR_SLICE:.*]] = fir.slice
+! CHECK: fir.if
+! CHECK: %[[OFFSET_GABOR_BOX:.*]] = fir.convert %[[OFFSET_GABOR_DESC:[^ ]+]]
+! CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+! CHECK: %[[OFFSET_GABOR_BYTES:.*]] = fir.box_addr %[[OFFSET_GABOR_BOX]]
+! CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+! CHECK: %[[OFFSET_GABOR_ADDRESS:.*]] = fir.coordinate_of %[[OFFSET_GABOR_BYTES]],
+! CHECK-SAME: -> !fir.ref<i8>
+! CHECK-NEXT: %[[OFFSET_GABOR_FAST:.*]] = fir.convert %[[OFFSET_GABOR_ADDRESS]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[OFFSET_GABOR_FAST]])
+! CHECK: } else {
+! CHECK: %[[OFFSET_GABOR_FALLBACK:.*]] = fir.array_coor %[[OFFSET_GABOR_DESC]]{{.*}}[%[[OFFSET_GABOR_SLICE]]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[OFFSET_GABOR_FALLBACK]])
+
+! CHECK-LABEL: func.func @_QPrank2_patterns(
+! CHECK: %[[RANK2_SLICE0:.*]] = fir.slice
+! CHECK: fir.if
+! CHECK: %[[RANK2_BOX0:.*]] = fir.convert %[[RANK2_DESC:[^ ]+]]
+! CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+! CHECK: %[[RANK2_BYTES0:.*]] = fir.box_addr %[[RANK2_BOX0]]
+! CHECK: %[[RANK2_ADDRESS0:.*]] = fir.coordinate_of %[[RANK2_BYTES0]],
+! CHECK-SAME: -> !fir.ref<i8>
+! CHECK-NEXT: %[[RANK2_FAST0:.*]] = fir.convert %[[RANK2_ADDRESS0]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[RANK2_FAST0]])
+! CHECK: } else {
+! CHECK: %[[RANK2_FALLBACK0:.*]] = fir.array_coor %[[RANK2_DESC]]{{.*}}[%[[RANK2_SLICE0]]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[RANK2_FALLBACK0]])
+! CHECK: %[[RANK2_SLICE1:.*]] = fir.slice
+! CHECK: fir.if
+! CHECK: %[[RANK2_BOX1:.*]] = fir.convert %[[RANK2_DESC]]
+! CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+! CHECK: %[[RANK2_BYTES1:.*]] = fir.box_addr %[[RANK2_BOX1]]
+! CHECK: %[[RANK2_ADDRESS1:.*]] = fir.coordinate_of %[[RANK2_BYTES1]],
+! CHECK-SAME: -> !fir.ref<i8>
+! CHECK-NEXT: %[[RANK2_FAST1:.*]] = fir.convert %[[RANK2_ADDRESS1]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[RANK2_FAST1]])
+! CHECK: } else {
+! CHECK: %[[RANK2_FALLBACK1:.*]] = fir.array_coor %[[RANK2_DESC]]{{.*}}[%[[RANK2_SLICE1]]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[RANK2_FALLBACK1]])
+
+! CHECK-LABEL: func.func @_QPgeneralized_slice(
+! CHECK: %[[GENERAL_SLICE:.*]] = fir.slice
+! CHECK: fir.if
+! CHECK: %[[GENERAL_BOX:.*]] = fir.convert %[[GENERAL_DESC:[^ ]+]]
+! CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+! CHECK: %[[GENERAL_BYTES:.*]] = fir.box_addr %[[GENERAL_BOX]]
+! CHECK: %[[GENERAL_ADDRESS:.*]] = fir.coordinate_of %[[GENERAL_BYTES]],
+! CHECK-SAME: -> !fir.ref<i8>
+! CHECK-NEXT: %[[GENERAL_FAST:.*]] = fir.convert %[[GENERAL_ADDRESS]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[GENERAL_FAST]])
+! CHECK: } else {
+! CHECK: %[[GENERAL_FALLBACK:.*]] = fir.array_coor %[[GENERAL_DESC]]{{.*}}[%[[GENERAL_SLICE]]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[GENERAL_FALLBACK]])
+
+! CHECK-LABEL: func.func @_QPconstant_scalar(
+! CHECK: %[[CONSTANT_SLICE:.*]] = fir.slice
+! CHECK: fir.if
+! CHECK: %[[CONSTANT_BOX:.*]] = fir.convert %[[CONSTANT_DESC:[^ ]+]]
+! CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+! CHECK: %[[CONSTANT_BYTES:.*]] = fir.box_addr %[[CONSTANT_BOX]]
+! CHECK: %[[CONSTANT_ADDRESS:.*]] = fir.coordinate_of %[[CONSTANT_BYTES]],
+! CHECK-SAME: -> !fir.ref<i8>
+! CHECK-NEXT: %[[CONSTANT_FAST:.*]] = fir.convert %[[CONSTANT_ADDRESS]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[CONSTANT_FAST]])
+! CHECK: } else {
+! CHECK: %[[CONSTANT_FALLBACK:.*]] = fir.array_coor %[[CONSTANT_DESC]]{{.*}}[%[[CONSTANT_SLICE]]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[CONSTANT_FALLBACK]])
+
+! CHECK-LABEL: func.func @_QPisolated_descriptors(
+! CHECK: %[[GOOD_SLICE:.*]] = fir.slice
+! CHECK: %[[PRED:.*]] = arith.cmpi eq
+! CHECK: fir.if %[[PRED]]
+! CHECK: %[[GOOD_BOX:.*]] = fir.convert %[[GOOD_DESC:[^ ]+]]
+! CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+! CHECK: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BOX]]
+! CHECK: %[[GOOD_ADDRESS:.*]] = fir.coordinate_of %[[GOOD_BYTES]],
+! CHECK-SAME: -> !fir.ref<i8>
+! CHECK-NEXT: %[[GOOD_FAST:.*]] = fir.convert %[[GOOD_ADDRESS]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[GOOD_FAST]])
+! CHECK: } else {
+! CHECK: %[[GOOD_FALLBACK:.*]] = fir.array_coor %[[GOOD_DESC]]{{.*}}[%[[GOOD_SLICE]]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[GOOD_FALLBACK]])
+! CHECK: %[[STRIDED_SLICE:.*]] = fir.slice
+! CHECK-NOT: fir.if
+! CHECK: %[[STRIDED_ACCESS:.*]] = fir.array_coor %[[STRIDED_DESC:[^ ]+]]{{.*}}[%[[STRIDED_SLICE]]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[STRIDED_ACCESS]])
+! CHECK-NOT: fir.if
+
+! CHECK-LABEL: func.func @_QPsequential_owners(
+! CHECK: %[[SEQUENTIAL_SLICE0:.*]] = fir.slice
+! CHECK: fir.if
+! CHECK: %[[SEQUENTIAL_BOX0:.*]] = fir.convert %[[SEQUENTIAL_DESC:[^ ]+]]
+! CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+! CHECK: %[[SEQUENTIAL_BYTES0:.*]] = fir.box_addr %[[SEQUENTIAL_BOX0]]
+! CHECK: %[[SEQUENTIAL_ADDRESS0:.*]] = fir.coordinate_of %[[SEQUENTIAL_BYTES0]],
+! CHECK-SAME: -> !fir.ref<i8>
+! CHECK-NEXT: %[[SEQUENTIAL_FAST0:.*]] = fir.convert %[[SEQUENTIAL_ADDRESS0]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[SEQUENTIAL_FAST0]])
+! CHECK: } else {
+! CHECK: %[[SEQUENTIAL_FALLBACK0:.*]] = fir.array_coor %[[SEQUENTIAL_DESC]]{{.*}}[%[[SEQUENTIAL_SLICE0]]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[SEQUENTIAL_FALLBACK0]])
+! CHECK: %[[SEQUENTIAL_SLICE1:.*]] = fir.slice
+! CHECK: fir.if
+! CHECK: %[[SEQUENTIAL_BOX1:.*]] = fir.convert %[[SEQUENTIAL_DESC]]
+! CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+! CHECK: %[[SEQUENTIAL_BYTES1:.*]] = fir.box_addr %[[SEQUENTIAL_BOX1]]
+! CHECK: %[[SEQUENTIAL_ADDRESS1:.*]] = fir.coordinate_of %[[SEQUENTIAL_BYTES1]],
+! CHECK-SAME: -> !fir.ref<i8>
+! CHECK-NEXT: %[[SEQUENTIAL_FAST1:.*]] = fir.convert %[[SEQUENTIAL_ADDRESS1]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[SEQUENTIAL_FAST1]])
+! CHECK: } else {
+! CHECK: %[[SEQUENTIAL_FALLBACK1:.*]] = fir.array_coor %[[SEQUENTIAL_DESC]]{{.*}}[%[[SEQUENTIAL_SLICE1]]]
+! CHECK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[SEQUENTIAL_FALLBACK1]])
diff --git a/flang/test/Transforms/loop-versioning-unit-slices.fir b/flang/test/Transforms/loop-versioning-unit-slices.fir
new file mode 100644
index 0000000000000..3bba38378abc1
--- /dev/null
+++ b/flang/test/Transforms/loop-versioning-unit-slices.fir
@@ -0,0 +1,3078 @@
+// RUN: fir-opt --loop-versioning %s -o %t
+// RUN: FileCheck %s --input-file=%t --enable-var-scope
+// RUN: FileCheck %s --input-file=%t --check-prefix=SLICE-ARITH \
+// RUN: --enable-var-scope
+// RUN: fir-opt '--loop-versioning=enable-slices=false' %s | \
+// RUN: FileCheck %s --check-prefix=DISABLED --enable-var-scope
+// RUN: fir-opt --canonicalize --loop-versioning %s | \
+// RUN: FileCheck %s --check-prefix=CANONICALIZED --enable-var-scope
+
+// Exercise accepted slice address forms, descriptor-wide rejection, owner
+// selection, wrapper and type boundaries, and clone-local operands. The
+// prefix-specific runs verify the transformed arithmetic, explicit disabling,
+// and the canonicalized pipeline without duplicating the test IR.
+
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<>,
+ fir.defaultkind = "a1c4d8i4l4r4",
+ fir.kindmap = "i2:1,i3:0,i16:64",
+ llvm.data_layout = "e-p:64:64:64:64"
+} {
+ func.func private @use_i32(!fir.ref<i32>)
+ func.func private @use_volatile(!fir.ref<i32, volatile>)
+ func.func private @use_f32(!fir.ref<f32>)
+ func.func private @use_f64(!fir.ref<f64>)
+ func.func private @use_char(!fir.ref<!fir.char<1,?>>)
+ func.func private @use_record(!fir.ref<!fir.type<slice_element{i:i32}>>)
+
+ // Reduced from the facerec GraphSimFct loop. Graph(:,:,ig) and
+ // GaborTrafo(:,:,x,y) are two independent slices in one owner.
+ func.func @facerec_core(
+ %gabor: !fir.box<!fir.array<?x?x?x?xf32>>,
+ %graph: !fir.box<!fir.array<?x?x?xf32>>,
+ %ig: i64, %x: i64, %y: i64) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %graphShape = fir.shape %c8, %c8, %c8
+ : (index, index, index) -> !fir.shape<3>
+ %gaborShape = fir.shape %c8, %c8, %c8, %c8
+ : (index, index, index, index) -> !fir.shape<4>
+ %undef64 = fir.undefined i64
+ %graphSlice = fir.slice %c1, %c8, %c1, %c1, %c8, %c1,
+ %ig, %undef64, %undef64
+ : (index, index, index, index, index, index, i64, i64, i64)
+ -> !fir.slice<3>
+ %gaborSlice = fir.slice %c1, %c8, %c1, %c1, %c8, %c1,
+ %x, %undef64, %undef64,
+ %y, %undef64, %undef64
+ : (index, index, index, index, index, index,
+ i64, i64, i64, i64, i64, i64) -> !fir.slice<4>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ fir.do_loop %j = %c1 to %c8 step %c1 {
+ %graphAddress = fir.array_coor %graph(%graphShape) [%graphSlice]
+ %i, %j, %ig
+ : (!fir.box<!fir.array<?x?x?xf32>>, !fir.shape<3>, !fir.slice<3>,
+ index, index, i64) -> !fir.ref<f32>
+ %gaborAddress = fir.array_coor %gabor(%gaborShape) [%gaborSlice]
+ %i, %j, %x, %y
+ : (!fir.box<!fir.array<?x?x?x?xf32>>, !fir.shape<4>,
+ !fir.slice<4>, index, index, i64, i64) -> !fir.ref<f32>
+ fir.call @use_f32(%graphAddress) : (!fir.ref<f32>) -> ()
+ fir.call @use_f32(%gaborAddress) : (!fir.ref<f32>) -> ()
+ }
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @facerec_core(
+ // CHECK-SAME: %[[GABOR:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[GRAPH:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[IG:[^:]+]]: i64
+ // CHECK-SAME: %[[X:[^:]+]]: i64
+ // CHECK-SAME: %[[Y:[^:]+]]: i64
+ // CHECK: %[[GRAPH_SHAPE:.*]] = fir.shape
+ // CHECK: %[[GABOR_SHAPE:.*]] = fir.shape
+ // CHECK: %[[GRAPH_SLICE:.*]] = fir.slice
+ // CHECK: %[[GABOR_SLICE:.*]] = fir.slice
+ // CHECK: fir.do_loop %[[I:[^ ]+]] =
+ // CHECK: %[[GRAPH_DIM0:.*]] = arith.constant 0 : index
+ // CHECK: %[[GRAPH_D0:.*]]:3 = fir.box_dims %[[GRAPH]], %[[GRAPH_DIM0]]
+ // CHECK-NOT: fir.box_dims %[[GRAPH]],
+ // CHECK: %[[GRAPH_SIZE:.*]] = arith.constant 4 : index
+ // CHECK: %[[GRAPH_CMP:.*]] = arith.cmpi eq, %[[GRAPH_D0]]#2, %[[GRAPH_SIZE]]
+ // CHECK: %[[GABOR_DIM0:.*]] = arith.constant 0 : index
+ // CHECK: %[[GABOR_D0:.*]]:3 = fir.box_dims %[[GABOR]], %[[GABOR_DIM0]]
+ // CHECK-NOT: fir.box_dims %[[GABOR]],
+ // CHECK: %[[GABOR_SIZE:.*]] = arith.constant 4 : index
+ // CHECK: %[[GABOR_CMP:.*]] = arith.cmpi eq, %[[GABOR_D0]]#2, %[[GABOR_SIZE]]
+ // CHECK: %[[CONTIGUOUS:.*]] = arith.andi %[[GABOR_CMP]], %[[GRAPH_CMP]]
+ // CHECK: fir.if %[[CONTIGUOUS]]
+ // CHECK: %[[FAST_ONE:.*]] = arith.constant 1 : index
+ // CHECK: %[[GRAPH_DIM1:.*]] = arith.constant 1 : index
+ // CHECK: %[[GRAPH_D1:.*]]:3 = fir.box_dims %[[GRAPH]], %[[GRAPH_DIM1]]
+ // CHECK: %[[GRAPH_DIM2:.*]] = arith.constant 2 : index
+ // CHECK: %[[GRAPH_D2:.*]]:3 = fir.box_dims %[[GRAPH]], %[[GRAPH_DIM2]]
+ // CHECK-NOT: fir.box_dims %[[GRAPH]],
+ // CHECK: %[[GRAPH_BOX:.*]] = fir.convert %[[GRAPH]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[GRAPH_BYTES:.*]] = fir.box_addr %[[GRAPH_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[GABOR_DIM1:.*]] = arith.constant 1 : index
+ // CHECK: %[[GABOR_D1:.*]]:3 = fir.box_dims %[[GABOR]], %[[GABOR_DIM1]]
+ // CHECK: %[[GABOR_DIM2:.*]] = arith.constant 2 : index
+ // CHECK: %[[GABOR_D2:.*]]:3 = fir.box_dims %[[GABOR]], %[[GABOR_DIM2]]
+ // CHECK: %[[GABOR_DIM3:.*]] = arith.constant 3 : index
+ // CHECK: %[[GABOR_D3:.*]]:3 = fir.box_dims %[[GABOR]], %[[GABOR_DIM3]]
+ // CHECK-NOT: fir.box_dims %[[GABOR]],
+ // CHECK: %[[GABOR_BOX:.*]] = fir.convert %[[GABOR]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[GABOR_BYTES:.*]] = fir.box_addr %[[GABOR_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop %[[J:[^ ]+]] =
+ // CHECK-NOT: arith.constant 0 : index
+ // CHECK-NOT: arith.constant 1 : index
+ // CHECK: %[[GRAPH_D0_OFFSET:.*]] = arith.subi %[[I]], %[[FAST_ONE]] : index
+ // CHECK: %[[GRAPH_D0_BYTES:.*]] = arith.muli %[[GRAPH_D0_OFFSET]], %[[GRAPH_SIZE]] : index
+ // CHECK: %[[GRAPH_D1_OFFSET:.*]] = arith.subi %[[J]], %[[FAST_ONE]] : index
+ // CHECK: %[[GRAPH_D1_BYTES:.*]] = arith.muli %[[GRAPH_D1]]#2, %[[GRAPH_D1_OFFSET]] : index
+ // CHECK: %[[GRAPH_OUTER_BYTES:.*]] = arith.addi %[[GRAPH_D0_BYTES]], %[[GRAPH_D1_BYTES]] : index
+ // CHECK: %[[GRAPH_D2_INDEX:.*]] = fir.convert %[[IG]] : (i64) -> index
+ // CHECK: %[[GRAPH_D2_OFFSET:.*]] = arith.subi %[[GRAPH_D2_INDEX]], %[[FAST_ONE]] : index
+ // CHECK: %[[GRAPH_D2_BYTES:.*]] = arith.muli %[[GRAPH_D2]]#2, %[[GRAPH_D2_OFFSET]] : index
+ // CHECK: %[[GRAPH_TOTAL:.*]] = arith.addi %[[GRAPH_OUTER_BYTES]], %[[GRAPH_D2_BYTES]] : index
+ // CHECK: %[[GRAPH_BYTE_ADDRESS:.*]] = fir.coordinate_of %[[GRAPH_BYTES]], %[[GRAPH_TOTAL]]
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[GRAPH_FAST:.*]] = fir.convert %[[GRAPH_BYTE_ADDRESS]]
+ // CHECK-SAME: -> !fir.ref<f32>
+ // CHECK-NOT: arith.constant 0 : index
+ // CHECK-NOT: arith.constant 1 : index
+ // CHECK: %[[GABOR_D0_OFFSET:.*]] = arith.subi %[[I]], %[[FAST_ONE]] : index
+ // CHECK: %[[GABOR_D0_BYTES:.*]] = arith.muli %[[GABOR_D0_OFFSET]], %[[GABOR_SIZE]] : index
+ // CHECK: %[[GABOR_D1_OFFSET:.*]] = arith.subi %[[J]], %[[FAST_ONE]] : index
+ // CHECK: %[[GABOR_D1_BYTES:.*]] = arith.muli %[[GABOR_D1]]#2, %[[GABOR_D1_OFFSET]] : index
+ // CHECK: %[[GABOR_OUTER_BYTES:.*]] = arith.addi %[[GABOR_D0_BYTES]], %[[GABOR_D1_BYTES]] : index
+ // CHECK: %[[GABOR_D2_INDEX:.*]] = fir.convert %[[X]] : (i64) -> index
+ // CHECK: %[[GABOR_D2_OFFSET:.*]] = arith.subi %[[GABOR_D2_INDEX]], %[[FAST_ONE]] : index
+ // CHECK: %[[GABOR_D2_BYTES:.*]] = arith.muli %[[GABOR_D2]]#2, %[[GABOR_D2_OFFSET]] : index
+ // CHECK: %[[GABOR_THREE_D_BYTES:.*]] = arith.addi %[[GABOR_OUTER_BYTES]], %[[GABOR_D2_BYTES]] : index
+ // CHECK: %[[GABOR_D3_INDEX:.*]] = fir.convert %[[Y]] : (i64) -> index
+ // CHECK: %[[GABOR_D3_OFFSET:.*]] = arith.subi %[[GABOR_D3_INDEX]], %[[FAST_ONE]] : index
+ // CHECK: %[[GABOR_D3_BYTES:.*]] = arith.muli %[[GABOR_D3]]#2, %[[GABOR_D3_OFFSET]] : index
+ // CHECK: %[[GABOR_TOTAL:.*]] = arith.addi %[[GABOR_THREE_D_BYTES]], %[[GABOR_D3_BYTES]] : index
+ // CHECK: %[[GABOR_BYTE_ADDRESS:.*]] = fir.coordinate_of %[[GABOR_BYTES]], %[[GABOR_TOTAL]]
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[GABOR_FAST:.*]] = fir.convert %[[GABOR_BYTE_ADDRESS]]
+ // CHECK-SAME: -> !fir.ref<f32>
+ // CHECK: fir.call @use_f32(%[[GRAPH_FAST]])
+ // CHECK: fir.call @use_f32(%[[GABOR_FAST]])
+ // CHECK: } else {
+ // CHECK-NOT: fir.box_dims
+ // CHECK: fir.do_loop %[[SLOW_J:[^ ]+]] =
+ // CHECK: %[[GRAPH_FALLBACK:.*]] = fir.array_coor %[[GRAPH]](%[[GRAPH_SHAPE]]) [%[[GRAPH_SLICE]]] %[[I]], %[[SLOW_J]], %[[IG]]
+ // CHECK: %[[GABOR_FALLBACK:.*]] = fir.array_coor %[[GABOR]](%[[GABOR_SHAPE]]) [%[[GABOR_SLICE]]] %[[I]], %[[SLOW_J]], %[[X]], %[[Y]]
+ // CHECK: fir.call @use_f32(%[[GRAPH_FALLBACK]])
+ // CHECK: fir.call @use_f32(%[[GABOR_FALLBACK]])
+ // DISABLED-LABEL: func.func @facerec_core(
+ // DISABLED-SAME: %[[GABOR:[^:]+]]: !fir.box
+ // DISABLED-SAME: %[[GRAPH:[^:]+]]: !fir.box
+ // DISABLED-NOT: fir.if
+ // DISABLED: %[[GRAPH_ADDRESS:.*]] = fir.array_coor %[[GRAPH]]
+ // DISABLED: %[[GABOR_ADDRESS:.*]] = fir.array_coor %[[GABOR]]
+ // DISABLED: fir.call @use_f32(%[[GRAPH_ADDRESS]])
+ // DISABLED: fir.call @use_f32(%[[GABOR_ADDRESS]])
+ // DISABLED-NOT: fir.array_coor
+ // DISABLED-NOT: fir.call @use_f32
+ // DISABLED-NOT: fir.if
+ // DISABLED: return
+ // SLICE-ARITH-LABEL: func.func @facerec_core(
+ // SLICE-ARITH: fir.if
+ // SLICE-ARITH-NOT: arith.shrsi
+ // SLICE-ARITH-NOT: arith.shrui
+ // SLICE-ARITH-NOT: arith.divsi
+ // SLICE-ARITH-NOT: arith.divui
+ // SLICE-ARITH: } else {
+
+ // A different element type and a Section/Scalar/Section pattern demonstrate
+ // that acceptance is not tied to facerec ranks, f32, or prefix sections.
+ // Non-one and narrow signed section operands are widened before arithmetic.
+ func.func @generalized_shape(
+ %a: !fir.box<!fir.array<?x?x?xf64>>, %lower0: !fir.int<16>,
+ %sliceScalar: i32, %scalar: i32, %lower2: index, %outer: index) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %shape = fir.shape %c8, %c8, %c8
+ : (index, index, index) -> !fir.shape<3>
+ %undef32 = fir.undefined i32
+ %slice = fir.slice %lower0, %c8, %c1,
+ %sliceScalar, %undef32, %undef32,
+ %lower2, %c8, %c1
+ : (!fir.int<16>, index, index, i32, i32, i32, index, index, index)
+ -> !fir.slice<3>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a(%shape) [%slice] %i, %scalar, %outer
+ : (!fir.box<!fir.array<?x?x?xf64>>, !fir.shape<3>, !fir.slice<3>,
+ index, i32, index) -> !fir.ref<f64>
+ fir.call @use_f64(%address) : (!fir.ref<f64>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @generalized_shape(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[LOWER0:[^:]+]]: !fir.int<16>
+ // CHECK-SAME: %[[SLICE_SCALAR:[^:]+]]: i32
+ // CHECK-SAME: %[[SCALAR:[^:]+]]: i32
+ // CHECK-SAME: %[[LOWER2:[^:]+]]: index
+ // CHECK-SAME: %[[OUTER:[^:]+]]: index
+ // CHECK: %[[SHAPE:.*]] = fir.shape
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK: %[[DIM0:.*]] = arith.constant 0 : index
+ // CHECK: %[[D0:.*]]:3 = fir.box_dims %[[A]], %[[DIM0]]
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK: %[[SIZE:.*]] = arith.constant 8 : index
+ // CHECK: fir.if
+ // CHECK: %[[FAST_ONE:.*]] = arith.constant 1 : index
+ // CHECK: %[[DIM1:.*]] = arith.constant 1 : index
+ // CHECK: %[[D1:.*]]:3 = fir.box_dims %[[A]], %[[DIM1]]
+ // CHECK: %[[DIM2:.*]] = arith.constant 2 : index
+ // CHECK: %[[D2:.*]]:3 = fir.box_dims %[[A]], %[[DIM2]]
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK: %[[A_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[A_BYTES:.*]] = fir.box_addr %[[A_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop %[[I:[^ ]+]]
+ // CHECK: %[[I_OFFSET:.*]] = arith.subi %[[I]], %[[FAST_ONE]] : index
+ // CHECK: %[[LOWER0_INDEX:.*]] = fir.convert %[[LOWER0]] : (!fir.int<16>) -> index
+ // CHECK: %[[LOWER0_ADJUST:.*]] = arith.subi %[[LOWER0_INDEX]], %[[FAST_ONE]] : index
+ // CHECK: %[[INNER_OFFSET:.*]] = arith.addi %[[I_OFFSET]], %[[LOWER0_ADJUST]] : index
+ // CHECK: %[[INNER_BYTES:.*]] = arith.muli %[[INNER_OFFSET]], %[[SIZE]] : index
+ // CHECK: %[[SCALAR_INDEX:.*]] = fir.convert %[[SCALAR]] : (i32) -> index
+ // CHECK: %[[SCALAR_OFFSET:.*]] = arith.subi %[[SCALAR_INDEX]], %[[FAST_ONE]] : index
+ // CHECK-NOT: fir.convert %[[SLICE_SCALAR]]
+ // CHECK: %[[SCALAR_BYTES:.*]] = arith.muli %[[D1]]#2, %[[SCALAR_OFFSET]] : index
+ // CHECK: %[[PARTIAL:.*]] = arith.addi %[[INNER_BYTES]], %[[SCALAR_BYTES]] : index
+ // CHECK: %[[OUTER_INDEX:.*]] = arith.subi %[[OUTER]], %[[FAST_ONE]] : index
+ // CHECK: %[[LOWER2_ADJUST:.*]] = arith.subi %[[LOWER2]], %[[FAST_ONE]] : index
+ // CHECK: %[[OUTER_OFFSET:.*]] = arith.addi %[[OUTER_INDEX]], %[[LOWER2_ADJUST]] : index
+ // CHECK: %[[OUTER_BYTES:.*]] = arith.muli %[[D2]]#2, %[[OUTER_OFFSET]] : index
+ // CHECK: %[[TOTAL:.*]] = arith.addi %[[PARTIAL]], %[[OUTER_BYTES]] : index
+ // CHECK: %[[BYTE_ADDRESS:.*]] = fir.coordinate_of %[[A_BYTES]], %[[TOTAL]]
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[FAST:.*]] = fir.convert %[[BYTE_ADDRESS]]
+ // CHECK: fir.call @use_f64(%[[FAST]])
+ // CHECK: } else {
+ // CHECK: fir.do_loop %[[SLOW_I:[^ ]+]] =
+ // CHECK: %[[FALLBACK:.*]] = fir.array_coor %[[A]](%[[SHAPE]]) [%[[SLICE]]] %[[SLOW_I]], %[[SCALAR]], %[[OUTER]]
+ // CHECK: fir.call @use_f64(%[[FALLBACK]])
+ // DISABLED-LABEL: func.func @generalized_shape(
+ // DISABLED-SAME: %[[A:[^:]+]]: !fir.box
+ // DISABLED-NOT: fir.if
+ // DISABLED: %[[ADDRESS:.*]] = fir.array_coor %[[A]]
+ // DISABLED: fir.call @use_f64(%[[ADDRESS]])
+ // DISABLED-NOT: fir.array_coor
+ // DISABLED-NOT: fir.call @use_f64
+ // DISABLED-NOT: fir.if
+ // DISABLED: return
+
+ // Every physical access of one accepted descriptor retains its own facts.
+ // The first access is [Section, Scalar], while the second is
+ // [Section, Section], so reusing or swapping their plans changes the
+ // dimension-one address formula.
+ func.func @two_accesses_one_descriptor(
+ %a: !fir.box<!fir.array<?x?xi32>>, %scalar: index,
+ %sectionLower: index, %sectionIndex: index) {
+ %c1 = arith.constant 1 : index
+ %c1_i32 = arith.constant 1 : i32
+ %convertedOne = fir.convert %c1_i32 : (i32) -> index
+ %c8 = arith.constant 8 : index
+ %undef = fir.undefined index
+ %sectionScalar = fir.slice %c1, %c8, %c1,
+ %scalar, %undef, %undef
+ : (index, index, index, index, index, index) -> !fir.slice<2>
+ %sectionSection = fir.slice %convertedOne, %c8, %c1,
+ %sectionLower, %c8, %c1
+ : (index, index, index, index, index, index) -> !fir.slice<2>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %x = fir.array_coor %a [%sectionScalar] %i, %scalar
+ : (!fir.box<!fir.array<?x?xi32>>, !fir.slice<2>, index, index)
+ -> !fir.ref<i32>
+ %y = fir.array_coor %a [%sectionSection] %i, %sectionIndex
+ : (!fir.box<!fir.array<?x?xi32>>, !fir.slice<2>, index, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%x) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%y) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @two_accesses_one_descriptor(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[SCALAR:[^:]+]]: index
+ // CHECK-SAME: %[[SECTION_LOWER:[^:]+]]: index
+ // CHECK-SAME: %[[SECTION_INDEX:[^:]+]]: index
+ // CHECK: %[[C1_I32:.*]] = arith.constant 1 : i32
+ // CHECK: %[[CONVERTED_ONE:.*]] = fir.convert %[[C1_I32]] : (i32) -> index
+ // CHECK: %[[SECTION_SCALAR:.*]] = fir.slice
+ // CHECK: %[[SECTION_SECTION:.*]] = fir.slice
+ // CHECK: %[[D0:.*]]:3 = fir.box_dims %[[A]],
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK: %[[SIZE:.*]] = arith.constant 4 : index
+ // CHECK: %[[PRED:.*]] = arith.cmpi eq, %[[D0]]#2, %[[SIZE]] : index
+ // CHECK: fir.if %[[PRED]]
+ // CHECK: %[[FAST_ONE:.*]] = arith.constant 1 : index
+ // CHECK: %[[D1:.*]]:3 = fir.box_dims %[[A]],
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK: %[[BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[BYTES:.*]] = fir.box_addr %[[BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop %[[I:[^ ]+]]
+ // CHECK: %[[X_D0_OFFSET:.*]] = arith.subi %[[I]], %[[FAST_ONE]] : index
+ // CHECK: %[[X_D0_BYTES:.*]] = arith.muli %[[X_D0_OFFSET]], %[[SIZE]] : index
+ // CHECK: %[[X_OFFSET:.*]] = arith.subi %[[SCALAR]], %[[FAST_ONE]] : index
+ // CHECK: %[[X_BYTES:.*]] = arith.muli %[[D1]]#2, %[[X_OFFSET]] : index
+ // CHECK: %[[X_TOTAL:.*]] = arith.addi %[[X_D0_BYTES]], %[[X_BYTES]] : index
+ // CHECK: %[[X_ADDRESS:.*]] = fir.coordinate_of %[[BYTES]], %[[X_TOTAL]]
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[X_REF:.*]] = fir.convert %[[X_ADDRESS]]
+ // CHECK-NOT: fir.array_coor
+ // CHECK: %[[Y_D0_OFFSET:.*]] = arith.subi %[[I]], %[[FAST_ONE]] : index
+ // CHECK-NOT: arith.subi %[[CONVERTED_ONE]],
+ // CHECK: %[[Y_D0_BYTES:.*]] = arith.muli %[[Y_D0_OFFSET]], %[[SIZE]] : index
+ // CHECK: %[[Y_INDEX_OFFSET:.*]] = arith.subi %[[SECTION_INDEX]], %[[FAST_ONE]] : index
+ // CHECK: %[[Y_LOWER_OFFSET:.*]] = arith.subi %[[SECTION_LOWER]], %[[FAST_ONE]] : index
+ // CHECK: %[[Y_OFFSET:.*]] = arith.addi %[[Y_INDEX_OFFSET]], %[[Y_LOWER_OFFSET]] : index
+ // CHECK: %[[Y_BYTES:.*]] = arith.muli %[[D1]]#2, %[[Y_OFFSET]] : index
+ // CHECK: %[[Y_TOTAL:.*]] = arith.addi %[[Y_D0_BYTES]], %[[Y_BYTES]] : index
+ // CHECK: %[[Y_ADDRESS:.*]] = fir.coordinate_of %[[BYTES]], %[[Y_TOTAL]]
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[Y_REF:.*]] = fir.convert %[[Y_ADDRESS]]
+ // CHECK-NOT: fir.array_coor
+ // CHECK: fir.call @use_i32(%[[X_REF]])
+ // CHECK: fir.call @use_i32(%[[Y_REF]])
+ // CHECK-NOT: fir.array_coor
+ // CHECK: } else {
+ // CHECK: %[[X_FALLBACK:.*]] = fir.array_coor %[[A]]{{.*}}[%[[SECTION_SCALAR]]]
+ // CHECK: %[[Y_FALLBACK:.*]] = fir.array_coor %[[A]]{{.*}}[%[[SECTION_SECTION]]]
+ // CHECK: fir.call @use_i32(%[[X_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[Y_FALLBACK]])
+ // DISABLED-LABEL: func.func @two_accesses_one_descriptor(
+ // DISABLED-SAME: %[[A:[^:]+]]: !fir.box
+ // DISABLED-NOT: fir.if
+ // DISABLED: %[[X_ADDRESS:.*]] = fir.array_coor %[[A]]
+ // DISABLED: %[[Y_ADDRESS:.*]] = fir.array_coor %[[A]]
+ // DISABLED: fir.call @use_i32(%[[X_ADDRESS]])
+ // DISABLED: fir.call @use_i32(%[[Y_ADDRESS]])
+ // DISABLED-NOT: fir.array_coor
+ // DISABLED-NOT: fir.call @use_i32
+ // DISABLED-NOT: fir.if
+ // DISABLED: return
+
+ // Both physical accesses intentionally share one fir.slice. Preflight
+ // classifies its triples once, while retaining separate coordinate checks
+ // and access-local byte offsets.
+ func.func @two_accesses_shared_slice(
+ %a: !fir.box<!fir.array<?xi32>>, %other: index) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %shared = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %x = fir.array_coor %a [%shared] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %y = fir.array_coor %a [%shared] %other
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%x) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%y) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @two_accesses_shared_slice(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[OTHER:[^:]+]]: index
+ // CHECK: %[[SHARED:.*]] = fir.slice
+ // CHECK: %[[D0:.*]]:3 = fir.box_dims %[[A]],
+ // CHECK: %[[SIZE:.*]] = arith.constant 4 : index
+ // CHECK: %[[PRED:.*]] = arith.cmpi eq, %[[D0]]#2, %[[SIZE]] : index
+ // CHECK: fir.if %[[PRED]]
+ // CHECK: %[[ONE:.*]] = arith.constant 1 : index
+ // CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop %[[I:[^ ]+]]
+ // CHECK: %[[X_OFFSET:.*]] = arith.subi %[[I]], %[[ONE]] : index
+ // CHECK: %[[X_BYTES:.*]] = arith.muli %[[X_OFFSET]], %[[SIZE]] : index
+ // CHECK: %[[X_ADDRESS:.*]] = fir.coordinate_of %[[BYTES]], %[[X_BYTES]]
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[X_REF:.*]] = fir.convert %[[X_ADDRESS]]
+ // CHECK: %[[Y_OFFSET:.*]] = arith.subi %[[OTHER]], %[[ONE]] : index
+ // CHECK: %[[Y_BYTES:.*]] = arith.muli %[[Y_OFFSET]], %[[SIZE]] : index
+ // CHECK: %[[Y_ADDRESS:.*]] = fir.coordinate_of %[[BYTES]], %[[Y_BYTES]]
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[Y_REF:.*]] = fir.convert %[[Y_ADDRESS]]
+ // CHECK: fir.call @use_i32(%[[X_REF]])
+ // CHECK: fir.call @use_i32(%[[Y_REF]])
+ // CHECK: } else {
+ // CHECK: %[[X_FALLBACK:.*]] = fir.array_coor %[[A]]{{.*}}[%[[SHARED]]]
+ // CHECK: %[[Y_FALLBACK:.*]] = fir.array_coor %[[A]]{{.*}}[%[[SHARED]]]
+ // CHECK: fir.call @use_i32(%[[X_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[Y_FALLBACK]])
+ // DISABLED-LABEL: func.func @two_accesses_shared_slice(
+ // DISABLED-SAME: %[[A:[^:]+]]: !fir.box
+ // DISABLED-NOT: fir.if
+ // DISABLED: %[[X_ADDRESS:.*]] = fir.array_coor %[[A]]
+ // DISABLED: %[[Y_ADDRESS:.*]] = fir.array_coor %[[A]]
+ // DISABLED: fir.call @use_i32(%[[X_ADDRESS]])
+ // DISABLED: fir.call @use_i32(%[[Y_ADDRESS]])
+ // DISABLED-NOT: fir.array_coor
+ // DISABLED-NOT: fir.call @use_i32
+ // DISABLED-NOT: fir.if
+ // DISABLED: return
+
+ // A constant-one leading coordinate with a constant-one section lower has
+ // zero byte offset in dimension zero. It still uses the common guarded
+ // rewrite; constant folding removes only its zero address contribution.
+ func.func @zero_leading_offset(
+ %a: !fir.box<!fir.array<?x?xi32>>, %outer: index) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %undef = fir.undefined index
+ %slice = fir.slice %c1, %c8, %c1, %undef, %undef, %undef
+ : (index, index, index, index, index, index) -> !fir.slice<2>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %c1, %outer
+ : (!fir.box<!fir.array<?x?xi32>>, !fir.slice<2>, index, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @zero_leading_offset(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[OUTER:[^:]+]]: index
+ // CHECK: %[[D0:.*]]:3 = fir.box_dims %[[A]],
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK: %[[SIZE:.*]] = arith.constant 4 : index
+ // CHECK: %[[PRED:.*]] = arith.cmpi eq, %[[D0]]#2, %[[SIZE]] : index
+ // CHECK: fir.if %[[PRED]]
+ // CHECK: %[[ONE:.*]] = arith.constant 1 : index
+ // CHECK: %[[D1:.*]]:3 = fir.box_dims %[[A]],
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+ // CHECK: fir.do_loop
+ // CHECK: %[[OUTER_OFFSET:.*]] = arith.subi %[[OUTER]], %[[ONE]] : index
+ // CHECK: %[[OUTER_BYTES:.*]] = arith.muli %[[D1]]#2, %[[OUTER_OFFSET]] : index
+ // CHECK: %[[BYTE_ADDRESS:.*]] = fir.coordinate_of %[[BYTES]], %[[OUTER_BYTES]]
+ // CHECK: %[[ADDRESS:.*]] = fir.convert %[[BYTE_ADDRESS]]
+ // CHECK: fir.call @use_i32(%[[ADDRESS]])
+ // CHECK-NOT: fir.slice
+ // CHECK: } else {
+ // CHECK: fir.do_loop
+ // CHECK: %[[FALLBACK:.*]] = fir.array_coor %[[A]]
+ // CHECK: fir.call @use_i32(%[[FALLBACK]])
+ // CHECK: return
+ // DISABLED-LABEL: func.func @zero_leading_offset(
+ // DISABLED-SAME: %[[A:[^:]+]]: !fir.box
+ // DISABLED-NOT: fir.if
+ // DISABLED: %[[ADDRESS:.*]] = fir.array_coor %[[A]]
+ // DISABLED: fir.call @use_i32(%[[ADDRESS]])
+ // DISABLED-NOT: fir.array_coor
+ // DISABLED-NOT: fir.call @use_i32
+ // DISABLED-NOT: fir.if
+ // DISABLED: return
+
+ // One unsupported sibling rejects the complete concrete descriptor.
+ func.func @same_descriptor_unsupported_sibling(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c8 = arith.constant 8 : index
+ %good = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ %bad = fir.slice %c1, %c8, %c2
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %x = fir.array_coor %a [%good] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %y = fir.array_coor %a [%bad] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%x) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%y) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @same_descriptor_unsupported_sibling(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[GOOD_SLICE:.*]] = fir.slice
+ // CHECK: %[[BAD_SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop
+ // CHECK: %[[GOOD_ADDRESS:.*]] = fir.array_coor %[[A]]{{.*}}[%[[GOOD_SLICE]]]
+ // CHECK: %[[BAD_ADDRESS:.*]] = fir.array_coor %[[A]]{{.*}}[%[[BAD_SLICE]]]
+ // CHECK: fir.call @use_i32(%[[GOOD_ADDRESS]])
+ // CHECK: fir.call @use_i32(%[[BAD_ADDRESS]])
+ // CHECK-NOT: fir.if
+ // CHECK: return
+
+ // A flat sibling of either indexing operation kind is outside the complete
+ // sliced-descriptor contract, regardless of which access is discovered
+ // first.
+ func.func @mixed_flat_and_slice_rejected(
+ %flatFirst: !fir.box<!fir.array<?xi32>>,
+ %sliceFirst: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %flatA = fir.array_coor %flatFirst %i
+ : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+ %sliceA = fir.array_coor %flatFirst [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %sliceB = fir.array_coor %sliceFirst [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %flatB = fir.coordinate_of %sliceFirst, %i
+ : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+ fir.call @use_i32(%flatA) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%sliceA) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%sliceB) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%flatB) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @mixed_flat_and_slice_rejected(
+ // CHECK-SAME: %[[FLAT_FIRST:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[SLICE_FIRST:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:[^ ]+]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[FLAT_FIRST]],
+ // CHECK-NOT: fir.box_dims %[[SLICE_FIRST]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop %[[I:[^ ]+]] =
+ // CHECK: %[[FLAT_A:[^ ]+]] = fir.array_coor %[[FLAT_FIRST]] %[[I]]
+ // CHECK: %[[SLICE_A:[^ ]+]] = fir.array_coor %[[FLAT_FIRST]] [%[[SLICE]]] %[[I]]
+ // CHECK: %[[SLICE_B:[^ ]+]] = fir.array_coor %[[SLICE_FIRST]] [%[[SLICE]]] %[[I]]
+ // CHECK: %[[FLAT_B:[^ ]+]] = fir.coordinate_of %[[SLICE_FIRST]], %[[I]]
+ // CHECK: fir.call @use_i32(%[[FLAT_A]])
+ // CHECK: fir.call @use_i32(%[[SLICE_A]])
+ // CHECK: fir.call @use_i32(%[[SLICE_B]])
+ // CHECK: fir.call @use_i32(%[[FLAT_B]])
+ // CHECK-NOT: fir.if
+ // CHECK: return
+
+ // A flat sibling owner leaves no complete sliced-owner set. The
+ // sliced owner stays generic, while the independent flat owner may retain
+ // the existing slice-free versioning path.
+ func.func @sibling_flat_owner_rejects_slices(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %sliced = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%sliced) : (!fir.ref<i32>) -> ()
+ }
+ fir.do_loop %j = %c1 to %c8 step %c1 {
+ %flat = fir.array_coor %a %j
+ : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+ fir.call @use_i32(%flat) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @sibling_flat_owner_rejects_slices(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK-NOT: !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[SLICED:.*]] = fir.array_coor %[[A]]{{.*}}[%[[SLICE]]]
+ // CHECK-NOT: !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[D0:.*]]:3 = fir.box_dims %[[A]],
+ // CHECK: %[[SIZE:.*]] = arith.constant 4 : index
+ // CHECK: %[[PRED:.*]] = arith.cmpi eq, %[[D0]]#2, %[[SIZE]] : index
+ // CHECK: fir.if %[[PRED]]
+ // CHECK: %[[FLAT_BOX:.*]] = fir.convert %[[A]]
+ // CHECK: %[[FLAT_BASE:.*]] = fir.box_addr %[[FLAT_BOX]]
+ // CHECK: %[[FLAT_ADDRESS:.*]] = fir.coordinate_of %[[FLAT_BASE]],
+ // CHECK: fir.call @use_i32(%[[FLAT_ADDRESS]])
+ // CHECK: } else {
+ // CHECK: %[[FLAT_FALLBACK:.*]] = fir.array_coor %[[A]]
+ // CHECK: fir.call @use_i32(%[[FLAT_FALLBACK]])
+ // CHECK-NOT: !fir.ref<!fir.array<?xi8>>
+ // CHECK: return
+
+ // Descriptor-wide rejection uses the concrete wrapper identity. A sliced
+ // owner and a slice-free owner of the same declared descriptor must not let
+ // normalization hide the conflict; only the slice-free owner is versioned.
+ func.func @wrapped_flat_owner_rejects_slices(
+ %root: !fir.box<!fir.array<?xi32>>) {
+ %declared = fir.declare %root {uniq_name = "wrapped"}
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %sliced = fir.array_coor %declared [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%sliced) : (!fir.ref<i32>) -> ()
+ }
+ fir.do_loop %j = %c1 to %c8 step %c1 {
+ %flat = fir.array_coor %declared %j
+ : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+ fir.call @use_i32(%flat) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @wrapped_flat_owner_rejects_slices(
+ // CHECK-SAME: %[[ROOT:[^:]+]]: !fir.box
+ // CHECK: %[[DECLARED:.*]] = fir.declare %[[ROOT]]
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK-NOT: !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[SLICED:.*]] = fir.array_coor %[[DECLARED]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.call @use_i32(%[[SLICED]])
+ // CHECK-NOT: !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[D0:.*]]:3 = fir.box_dims %[[DECLARED]],
+ // CHECK: %[[SIZE:.*]] = arith.constant 4 : index
+ // CHECK: %[[PRED:.*]] = arith.cmpi eq, %[[D0]]#2, %[[SIZE]] : index
+ // CHECK: fir.if %[[PRED]]
+ // CHECK: %[[FLAT_BOX:.*]] = fir.convert %[[DECLARED]]
+ // CHECK: %[[FLAT_BASE:.*]] = fir.box_addr %[[FLAT_BOX]]
+ // CHECK: %[[FLAT_ADDRESS:.*]] = fir.coordinate_of %[[FLAT_BASE]],
+ // CHECK: fir.call @use_i32(%[[FLAT_ADDRESS]])
+ // CHECK: } else {
+ // CHECK: %[[FLAT_FALLBACK:.*]] = fir.array_coor %[[DECLARED]]
+ // CHECK: fir.call @use_i32(%[[FLAT_FALLBACK]])
+ // CHECK-NOT: !fir.ref<!fir.array<?xi8>>
+ // CHECK: return
+
+ // A sliced descriptor may be published only when every owner is entirely
+ // slice-based. The second owner also contains independent slice-free work,
+ // so both sliced uses of %a remain generic while %b is still versioned.
+ func.func @publication_rejects_incomplete_owner_set(
+ %a: !fir.box<!fir.array<?xi32>>,
+ %b: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %first = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%first) : (!fir.ref<i32>) -> ()
+ }
+ fir.do_loop %j = %c1 to %c8 step %c1 {
+ %second = fir.array_coor %a [%slice] %j
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %flat = fir.array_coor %b %j
+ : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+ fir.call @use_i32(%second) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%flat) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @publication_rejects_incomplete_owner_set(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[B:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK: fir.do_loop %[[I:[^ ]+]] =
+ // CHECK: %[[FIRST:.*]] = fir.array_coor %[[A]] [%[[SLICE]]] %[[I]]
+ // CHECK: fir.call @use_i32(%[[FIRST]])
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK: fir.box_dims %[[B]],
+ // CHECK: fir.if
+ // CHECK: %[[B_BOX:.*]] = fir.convert %[[B]]
+ // CHECK: %[[B_BASE:.*]] = fir.box_addr %[[B_BOX]]
+ // CHECK: fir.do_loop %[[FAST_J:[^ ]+]] =
+ // CHECK: %[[SECOND_FAST:.*]] = fir.array_coor %[[A]] [%[[SLICE]]] %[[FAST_J]]
+ // CHECK: %[[B_FAST:.*]] = fir.coordinate_of %[[B_BASE]],
+ // CHECK: fir.call @use_i32(%[[SECOND_FAST]])
+ // CHECK: fir.call @use_i32(%[[B_FAST]])
+ // CHECK: } else {
+ // CHECK: fir.do_loop %[[FALLBACK_J:[^ ]+]] =
+ // CHECK: %[[SECOND_FALLBACK:.*]] = fir.array_coor %[[A]] [%[[SLICE]]] %[[FALLBACK_J]]
+ // CHECK: %[[B_FALLBACK:.*]] = fir.array_coor %[[B]] %[[FALLBACK_J]]
+ // CHECK: fir.call @use_i32(%[[SECOND_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[B_FALLBACK]])
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK: return
+
+ // Rejection is descriptor-local: an independent unit slice remains
+ // versionable in the same owner.
+ func.func @descriptor_independence(
+ %badArg: !fir.box<!fir.array<?xi32>>,
+ %goodArg: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c8 = arith.constant 8 : index
+ %bad = fir.slice %c1, %c8, %c2
+ : (index, index, index) -> !fir.slice<1>
+ %good = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %x = fir.array_coor %badArg [%bad] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %y = fir.array_coor %goodArg [%good] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%x) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%y) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @descriptor_independence(
+ // CHECK-SAME: %[[BAD:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[GOOD:[^:]+]]: !fir.box
+ // CHECK: %[[BAD_SLICE:.*]] = fir.slice
+ // CHECK: %[[GOOD_SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: %[[GOOD_D0:.*]]:3 = fir.box_dims %[[GOOD]],
+ // CHECK: %[[GOOD_SIZE:.*]] = arith.constant 4 : index
+ // CHECK: %[[GOOD_PRED:.*]] = arith.cmpi eq, %[[GOOD_D0]]#2, %[[GOOD_SIZE]] : index
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: fir.if %[[GOOD_PRED]]
+ // CHECK: %[[GOOD_BYTE_BOX:.*]] = fir.convert %[[GOOD]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.array_coor %[[BAD]]{{.*}}[%[[BAD_SLICE]]]
+ // CHECK: fir.coordinate_of %[[GOOD_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: } else {
+ // CHECK: fir.array_coor %[[BAD]]{{.*}}[%[[BAD_SLICE]]]
+ // CHECK: fir.array_coor %[[GOOD]]{{.*}}[%[[GOOD_SLICE]]]
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: return
+
+ // Canonicalization may fold the converted i1-rooted step, but must not make
+ // that descriptor eligible. The independent ordinary unit slice still is.
+ // CANONICALIZED-LABEL: func.func @i1_operands_rejected(
+ // CANONICALIZED-SAME: %[[BAD_STEP:[^:]+]]: !fir.box
+ // CANONICALIZED-SAME: %[[BAD_INDEX:[^:]+]]: !fir.box
+ // CANONICALIZED-SAME: %[[BAD_LOWER:[^:]+]]: !fir.box
+ // CANONICALIZED-SAME: %[[CONVERTED_STEP:[^:]+]]: !fir.box
+ // CANONICALIZED-SAME: %[[GOOD:[^:]+]]: !fir.box
+ // CANONICALIZED-NOT: fir.box_dims %[[BAD_STEP]],
+ // CANONICALIZED-NOT: fir.box_dims %[[BAD_INDEX]],
+ // CANONICALIZED-NOT: fir.box_dims %[[BAD_LOWER]],
+ // CANONICALIZED-NOT: fir.box_dims %[[CONVERTED_STEP]],
+ // CANONICALIZED: fir.box_dims %[[GOOD]],
+ // CANONICALIZED-NOT: fir.box_dims %[[BAD_STEP]],
+ // CANONICALIZED-NOT: fir.box_dims %[[BAD_INDEX]],
+ // CANONICALIZED-NOT: fir.box_dims %[[BAD_LOWER]],
+ // CANONICALIZED-NOT: fir.box_dims %[[CONVERTED_STEP]],
+ // CANONICALIZED: return
+
+ // Reject raw i1 values in every consumed operand position. Also reject a
+ // conversion chain rooted at i1: canonicalization may combine it into a
+ // direct i1-to-index conversion and fold the set bit to -1. An independent
+ // descriptor with an ordinary unit step must remain eligible.
+ func.func @i1_operands_rejected(
+ %badStepArg: !fir.box<!fir.array<?xi32>>,
+ %badIndexArg: !fir.box<!fir.array<?xi32>>,
+ %badLowerArg: !fir.box<!fir.array<?xi32>>,
+ %convertedStepArg: !fir.box<!fir.array<?xi32>>,
+ %goodArg: !fir.box<!fir.array<?xi32>>) {
+ %true = arith.constant true
+ %c1 = arith.constant 1 : index
+ %wideTrue = fir.convert %true : (i1) -> i8
+ %convertedUnit = fir.convert %wideTrue : (i8) -> index
+ %c8 = arith.constant 8 : index
+ %badStep = fir.slice %c1, %c8, %true
+ : (index, index, i1) -> !fir.slice<1>
+ %badLower = fir.slice %true, %c8, %c1
+ : (i1, index, index) -> !fir.slice<1>
+ %converted = fir.slice %c1, %c8, %convertedUnit
+ : (index, index, index) -> !fir.slice<1>
+ %good = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %badStepAddress = fir.array_coor %badStepArg [%badStep] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %badIndexAddress = fir.array_coor %badIndexArg [%good] %true
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, i1)
+ -> !fir.ref<i32>
+ %badLowerAddress = fir.array_coor %badLowerArg [%badLower] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %convertedStepAddress = fir.array_coor %convertedStepArg [%converted] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %goodAddress = fir.array_coor %goodArg [%good] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%badStepAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%badIndexAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%badLowerAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%convertedStepAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%goodAddress) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @i1_operands_rejected(
+ // CHECK-SAME: %[[BAD_STEP:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[BAD_INDEX:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[BAD_LOWER:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[CONVERTED_STEP:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[GOOD:[^:]+]]: !fir.box
+ // CHECK: %[[TRUE:.*]] = arith.constant true
+ // CHECK: %[[C1:.*]] = arith.constant 1 : index
+ // CHECK: %[[WIDE_TRUE:.*]] = fir.convert %[[TRUE]] : (i1) -> i8
+ // CHECK: %[[UNIT:.*]] = fir.convert %[[WIDE_TRUE]] : (i8) -> index
+ // CHECK: %[[C8:.*]] = arith.constant 8 : index
+ // CHECK: %[[BAD_STEP_SLICE:.*]] = fir.slice %[[C1]], %[[C8]], %[[TRUE]]
+ // CHECK: %[[BAD_LOWER_SLICE:.*]] = fir.slice %[[TRUE]], %[[C8]], %[[C1]]
+ // CHECK: %[[CONVERTED_SLICE:.*]] = fir.slice %[[C1]], %[[C8]], %[[UNIT]]
+ // CHECK: %[[GOOD_SLICE:.*]] = fir.slice %[[C1]], %[[C8]], %[[C1]]
+ // CHECK-NOT: fir.box_dims %[[BAD_STEP]],
+ // CHECK-NOT: fir.box_dims %[[BAD_INDEX]],
+ // CHECK-NOT: fir.box_dims %[[BAD_LOWER]],
+ // CHECK-NOT: fir.box_dims %[[CONVERTED_STEP]],
+ // CHECK: fir.box_dims %[[GOOD]],
+ // CHECK-NOT: fir.box_dims %[[BAD_STEP]],
+ // CHECK-NOT: fir.box_dims %[[BAD_INDEX]],
+ // CHECK-NOT: fir.box_dims %[[BAD_LOWER]],
+ // CHECK-NOT: fir.box_dims %[[CONVERTED_STEP]],
+ // CHECK: fir.if
+ // CHECK: %[[GOOD_BYTE_BOX:.*]] = fir.convert %[[GOOD]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop %[[FAST_I:[^ ]+]] =
+ // CHECK: %[[BAD_STEP_FAST:.*]] = fir.array_coor %[[BAD_STEP]] [%[[BAD_STEP_SLICE]]] %[[FAST_I]]
+ // CHECK: %[[BAD_INDEX_FAST:.*]] = fir.array_coor %[[BAD_INDEX]] [%[[GOOD_SLICE]]] %[[TRUE]]
+ // CHECK: %[[BAD_LOWER_FAST:.*]] = fir.array_coor %[[BAD_LOWER]] [%[[BAD_LOWER_SLICE]]] %[[FAST_I]]
+ // CHECK: %[[CONVERTED_FAST:.*]] = fir.array_coor %[[CONVERTED_STEP]] [%[[CONVERTED_SLICE]]] %[[FAST_I]]
+ // CHECK-NOT: fir.array_coor %[[GOOD]]
+ // CHECK: %[[GOOD_BYTE_ADDRESS:.*]] = fir.coordinate_of %[[GOOD_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[GOOD_FAST:.*]] = fir.convert %[[GOOD_BYTE_ADDRESS]]
+ // CHECK: fir.call @use_i32(%[[BAD_STEP_FAST]])
+ // CHECK: fir.call @use_i32(%[[BAD_INDEX_FAST]])
+ // CHECK: fir.call @use_i32(%[[BAD_LOWER_FAST]])
+ // CHECK: fir.call @use_i32(%[[CONVERTED_FAST]])
+ // CHECK: fir.call @use_i32(%[[GOOD_FAST]])
+ // CHECK: } else {
+ // CHECK: fir.do_loop %[[SLOW_I:[^ ]+]] =
+ // CHECK: %[[BAD_STEP_FALLBACK:.*]] = fir.array_coor %[[BAD_STEP]] [%[[BAD_STEP_SLICE]]] %[[SLOW_I]]
+ // CHECK: %[[BAD_INDEX_FALLBACK:.*]] = fir.array_coor %[[BAD_INDEX]] [%[[GOOD_SLICE]]] %[[TRUE]]
+ // CHECK: %[[BAD_LOWER_FALLBACK:.*]] = fir.array_coor %[[BAD_LOWER]] [%[[BAD_LOWER_SLICE]]] %[[SLOW_I]]
+ // CHECK: %[[CONVERTED_FALLBACK:.*]] = fir.array_coor %[[CONVERTED_STEP]] [%[[CONVERTED_SLICE]]] %[[SLOW_I]]
+ // CHECK: %[[GOOD_FALLBACK:.*]] = fir.array_coor %[[GOOD]] [%[[GOOD_SLICE]]] %[[SLOW_I]]
+ // CHECK: fir.call @use_i32(%[[BAD_STEP_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[BAD_INDEX_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[BAD_LOWER_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[CONVERTED_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[GOOD_FALLBACK]])
+ // CHECK-NOT: fir.box_dims %[[BAD_STEP]],
+ // CHECK-NOT: fir.box_dims %[[BAD_INDEX]],
+ // CHECK-NOT: fir.box_dims %[[BAD_LOWER]],
+ // CHECK-NOT: fir.box_dims %[[CONVERTED_STEP]],
+ // CHECK: return
+
+ // A signed one-bit intermediate changes a positive one to -1 when it is
+ // widened. Preserve the section-lower adjustment instead of looking through
+ // such a conversion chain and eliding it.
+ func.func @signed_one_bit_convert_adjusted(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1_i32 = arith.constant 1 : i32
+ %signedBit = fir.convert %c1_i32 : (i32) -> si1
+ %lower = fir.convert %signedBit : (si1) -> i64
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %lower, %c8, %c1
+ : (i64, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @signed_one_bit_convert_adjusted(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[ONE_I32:.*]] = arith.constant 1 : i32
+ // CHECK: %[[SIGNED_BIT:.*]] = fir.convert %[[ONE_I32]] : (i32) -> si1
+ // CHECK: %[[LOWER:.*]] = fir.convert %[[SIGNED_BIT]] : (si1) -> i64
+ // CHECK: fir.box_dims %[[A]],
+ // CHECK: fir.if
+ // CHECK: %[[FAST_ONE:.*]] = arith.constant 1 : index
+ // CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop %[[I:[^ ]+]] =
+ // CHECK: %[[LOWER_INDEX:.*]] = fir.convert %[[LOWER]] : (i64) -> index
+ // CHECK: %[[LOWER_ADJUST:.*]] = arith.subi %[[LOWER_INDEX]], %[[FAST_ONE]] : index
+ // CHECK: arith.addi {{.*}}, %[[LOWER_ADJUST]] : index
+ // CHECK: fir.coordinate_of %[[BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: } else {
+ // CHECK: fir.array_coor %[[A]]
+
+ // FIR integer kinds are signed. With this kind mapping, !fir.int<2> lowers
+ // to i1, so widening its set bit produces -1. Preserve the section-lower
+ // adjustment just as for the builtin signed one-bit form above.
+ func.func @kind_mapped_one_bit_lower_adjusted(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1_i32 = arith.constant 1 : i32
+ %kindBit = fir.convert %c1_i32 : (i32) -> !fir.int<2>
+ %lower = fir.convert %kindBit : (!fir.int<2>) -> i64
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %lower, %c8, %c1
+ : (i64, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @kind_mapped_one_bit_lower_adjusted(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[ONE_I32:.*]] = arith.constant 1 : i32
+ // CHECK: %[[KIND_BIT:.*]] = fir.convert %[[ONE_I32]] : (i32) -> !fir.int<2>
+ // CHECK: %[[LOWER:.*]] = fir.convert %[[KIND_BIT]] : (!fir.int<2>) -> i64
+ // CHECK: fir.box_dims %[[A]],
+ // CHECK: fir.if
+ // CHECK: %[[FAST_ONE:.*]] = arith.constant 1 : index
+ // CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop %[[I:[^ ]+]] =
+ // CHECK: %[[LOWER_INDEX:.*]] = fir.convert %[[LOWER]] : (i64) -> index
+ // CHECK: %[[LOWER_ADJUST:.*]] = arith.subi %[[LOWER_INDEX]], %[[FAST_ONE]] : index
+ // CHECK: arith.addi {{.*}}, %[[LOWER_ADJUST]] : index
+ // CHECK: fir.coordinate_of %[[BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: } else {
+ // CHECK: fir.array_coor %[[A]]
+
+ // Proven source lowers equal to one do not participate in fast-path
+ // arithmetic, so their original integer widths do not restrict versioning.
+ func.func @static_one_lowers_ignore_source_width(
+ %narrowArg: !fir.box<!fir.array<?xi32>>,
+ %wideArg: !fir.box<!fir.array<?xi32>>) {
+ %narrowSource = arith.constant 1 : i16
+ %narrowLower = fir.convert %narrowSource : (i16) -> ui16
+ %wideLower = arith.constant 1 : i128
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %narrowSlice = fir.slice %narrowLower, %c8, %c1
+ : (ui16, index, index) -> !fir.slice<1>
+ %wideSlice = fir.slice %wideLower, %c8, %c1
+ : (i128, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %narrowAddress = fir.array_coor %narrowArg [%narrowSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %wideAddress = fir.array_coor %wideArg [%wideSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%narrowAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%wideAddress) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @static_one_lowers_ignore_source_width(
+ // CHECK-SAME: %[[NARROW_ARG:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[WIDE_ARG:[^:]+]]: !fir.box
+ // CHECK: %[[NARROW_SOURCE:.*]] = arith.constant 1 : i16
+ // CHECK: %[[NARROW_LOWER:.*]] = fir.convert %[[NARROW_SOURCE]] : (i16) -> ui16
+ // CHECK: %[[WIDE_LOWER:.*]] = arith.constant 1 : i128
+ // CHECK: %[[NARROW_SLICE:.*]] = fir.slice %[[NARROW_LOWER]],
+ // CHECK: %[[WIDE_SLICE:.*]] = fir.slice %[[WIDE_LOWER]],
+ // CHECK: fir.box_dims %[[NARROW_ARG]],
+ // CHECK: fir.box_dims %[[WIDE_ARG]],
+ // CHECK: fir.if
+ // CHECK: %[[NARROW_BYTE_BOX:.*]] = fir.convert %[[NARROW_ARG]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[NARROW_BYTES:.*]] = fir.box_addr %[[NARROW_BYTE_BOX]]
+ // CHECK: %[[WIDE_BYTE_BOX:.*]] = fir.convert %[[WIDE_ARG]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[WIDE_BYTES:.*]] = fir.box_addr %[[WIDE_BYTE_BOX]]
+ // CHECK: fir.do_loop
+ // CHECK-NOT: fir.convert %[[NARROW_LOWER]]
+ // CHECK-NOT: fir.convert %[[WIDE_LOWER]]
+ // CHECK-NOT: fir.array_coor %[[NARROW_ARG]]
+ // CHECK-NOT: fir.array_coor %[[WIDE_ARG]]
+ // CHECK: %[[NARROW_BYTE_ADDRESS:.*]] = fir.coordinate_of %[[NARROW_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[NARROW_FAST:.*]] = fir.convert %[[NARROW_BYTE_ADDRESS]]
+ // CHECK: %[[WIDE_BYTE_ADDRESS:.*]] = fir.coordinate_of %[[WIDE_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[WIDE_FAST:.*]] = fir.convert %[[WIDE_BYTE_ADDRESS]]
+ // CHECK: fir.call @use_i32(%[[NARROW_FAST]])
+ // CHECK-NEXT: fir.call @use_i32(%[[WIDE_FAST]])
+ // CHECK: } else {
+ // CHECK: %[[NARROW_FALLBACK:.*]] = fir.array_coor %[[NARROW_ARG]] [%[[NARROW_SLICE]]]
+ // CHECK: %[[WIDE_FALLBACK:.*]] = fir.array_coor %[[WIDE_ARG]] [%[[WIDE_SLICE]]]
+ // CHECK: fir.call @use_i32(%[[NARROW_FALLBACK]])
+ // CHECK-NEXT: fir.call @use_i32(%[[WIDE_FALLBACK]])
+ // CHECK: return
+
+ // A proven unit lower is irrelevant to address materialization, but it must
+ // not hide a non-unit section step. Reject only that descriptor while an
+ // independent unit-step descriptor in the same owner remains eligible.
+ func.func @static_one_lower_does_not_hide_nonunit_step(
+ %bad: !fir.box<!fir.array<?xi32>>,
+ %good: !fir.box<!fir.array<?xi32>>) {
+ %wideOne = arith.constant 1 : i128
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c8 = arith.constant 8 : index
+ %badSlice = fir.slice %wideOne, %c8, %c2
+ : (i128, index, index) -> !fir.slice<1>
+ %goodSlice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %badAddress = fir.array_coor %bad [%badSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %goodAddress = fir.array_coor %good [%goodSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%badAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%goodAddress) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @static_one_lower_does_not_hide_nonunit_step(
+ // CHECK-SAME: %[[BAD:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[GOOD:[^:]+]]: !fir.box
+ // CHECK: %[[BAD_SLICE:.*]] = fir.slice
+ // CHECK: %[[GOOD_SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: fir.box_dims %[[GOOD]],
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: fir.if
+ // CHECK: fir.array_coor %[[BAD]]{{.*}}[%[[BAD_SLICE]]]
+ // CHECK: fir.coordinate_of
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: } else {
+ // CHECK: fir.array_coor %[[BAD]]{{.*}}[%[[BAD_SLICE]]]
+ // CHECK: fir.array_coor %[[GOOD]]{{.*}}[%[[GOOD_SLICE]]]
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: return
+
+ // Non-unit lower bounds still require a supported address representation.
+ // Reject a too-wide signed value, a narrow unsigned value, and a FIR integer
+ // kind with no mapped width without affecting an independent descriptor.
+ func.func @unsupported_section_lowers_remain_rejected(
+ %wide: !fir.box<!fir.array<?xi32>>,
+ %unsigned: !fir.box<!fir.array<?xi32>>,
+ %zeroWidth: !fir.box<!fir.array<?xi32>>,
+ %good: !fir.box<!fir.array<?xi32>>) {
+ %wideLower = arith.constant 2 : i128
+ %unsignedSource = arith.constant 5 : i16
+ %unsignedLower = fir.convert %unsignedSource : (i16) -> ui16
+ %zeroSource = arith.constant 2 : i32
+ %zeroLower = fir.convert %zeroSource : (i32) -> !fir.int<3>
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %wideSlice = fir.slice %wideLower, %c8, %c1
+ : (i128, index, index) -> !fir.slice<1>
+ %unsignedSlice = fir.slice %unsignedLower, %c8, %c1
+ : (ui16, index, index) -> !fir.slice<1>
+ %zeroSlice = fir.slice %zeroLower, %c8, %c1
+ : (!fir.int<3>, index, index) -> !fir.slice<1>
+ %goodSlice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %wideAddress = fir.array_coor %wide [%wideSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %unsignedAddress = fir.array_coor %unsigned [%unsignedSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %zeroAddress = fir.array_coor %zeroWidth [%zeroSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %goodAddress = fir.array_coor %good [%goodSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%wideAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%unsignedAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%zeroAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%goodAddress) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @unsupported_section_lowers_remain_rejected(
+ // CHECK-SAME: %[[WIDE:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[UNSIGNED:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[ZERO:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[GOOD:[^:]+]]: !fir.box
+ // CHECK: %[[WIDE_SLICE:.*]] = fir.slice
+ // CHECK: %[[UNSIGNED_SLICE:.*]] = fir.slice
+ // CHECK: %[[ZERO_SLICE:.*]] = fir.slice
+ // CHECK: %[[GOOD_SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[WIDE]],
+ // CHECK-NOT: fir.box_dims %[[UNSIGNED]],
+ // CHECK-NOT: fir.box_dims %[[ZERO]],
+ // CHECK: fir.box_dims %[[GOOD]],
+ // CHECK-NOT: fir.box_dims %[[WIDE]],
+ // CHECK-NOT: fir.box_dims %[[UNSIGNED]],
+ // CHECK-NOT: fir.box_dims %[[ZERO]],
+ // CHECK: fir.if
+ // CHECK: fir.array_coor %[[WIDE]]{{.*}}[%[[WIDE_SLICE]]]
+ // CHECK: fir.array_coor %[[UNSIGNED]]{{.*}}[%[[UNSIGNED_SLICE]]]
+ // CHECK: fir.array_coor %[[ZERO]]{{.*}}[%[[ZERO_SLICE]]]
+ // CHECK: fir.coordinate_of
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: } else {
+ // CHECK: fir.array_coor %[[WIDE]]{{.*}}[%[[WIDE_SLICE]]]
+ // CHECK: fir.array_coor %[[UNSIGNED]]{{.*}}[%[[UNSIGNED_SLICE]]]
+ // CHECK: fir.array_coor %[[ZERO]]{{.*}}[%[[ZERO_SLICE]]]
+ // CHECK: fir.array_coor %[[GOOD]]{{.*}}[%[[GOOD_SLICE]]]
+ // CHECK: return
+
+ // A one-bit unsigned intermediate zero-extends to index. The resulting
+ // section lower is one, so it must not add a lower-bound adjustment.
+ func.func @converted_ui1_lower_is_one(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %source = arith.constant 1 : i8
+ %bit = fir.convert %source : (i8) -> ui1
+ %lower = fir.convert %bit : (ui1) -> index
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %lower, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @converted_ui1_lower_is_one(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[SOURCE:.*]] = arith.constant 1 : i8
+ // CHECK: %[[BIT:.*]] = fir.convert %[[SOURCE]] : (i8) -> ui1
+ // CHECK: %[[LOWER:.*]] = fir.convert %[[BIT]] : (ui1) -> index
+ // CHECK: %[[SLICE:.*]] = fir.slice %[[LOWER]],
+ // CHECK: fir.box_dims %[[A]],
+ // CHECK: fir.if
+ // CHECK: %[[FAST_ONE:.*]] = arith.constant 1 : index
+ // CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+ // CHECK: fir.do_loop %[[I:[^ ]+]] =
+ // CHECK-NOT: arith.subi %[[LOWER]],
+ // CHECK: %[[OFFSET:.*]] = arith.subi %[[I]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTE_ADDRESS:.*]] = fir.coordinate_of %[[BYTES]],
+ // CHECK: %[[FAST:.*]] = fir.convert %[[BYTE_ADDRESS]]
+ // CHECK-NEXT: fir.call @use_i32(%[[FAST]])
+ // CHECK: } else {
+ // CHECK: %[[FALLBACK:.*]] = fir.array_coor %[[A]] [%[[SLICE]]]
+ // CHECK-NEXT: fir.call @use_i32(%[[FALLBACK]])
+ // CHECK: return
+
+ // The same effective one-bit value cannot be accepted as a positive unit
+ // step. Keep this descriptor on the generic path.
+ func.func @kind_mapped_one_bit_step_rejected(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1_i32 = arith.constant 1 : i32
+ %kindBit = fir.convert %c1_i32 : (i32) -> !fir.int<2>
+ %step = fir.convert %kindBit : (!fir.int<2>) -> i64
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %step
+ : (index, index, i64) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @kind_mapped_one_bit_step_rejected(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.array_coor %[[A]]{{.*}}[%[[SLICE]]]
+ // CHECK: return
+
+ // A truncating conversion may produce a positive one even when its source
+ // constant is not one. Classify the final bit pattern and accept the slice.
+ func.func @truncated_static_one_step(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c257 = arith.constant 257 : i16
+ %step = fir.convert %c257 : (i16) -> i8
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %step
+ : (index, index, i8) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @truncated_static_one_step(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[SOURCE:.*]] = arith.constant 257 : i16
+ // CHECK: %[[STEP:.*]] = fir.convert %[[SOURCE]] : (i16) -> i8
+ // CHECK: %[[SLICE:.*]] = fir.slice {{.*}}%[[STEP]]
+ // CHECK: fir.box_dims %[[A]],
+ // CHECK: fir.if
+ // CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[FAST:.*]] = fir.coordinate_of %[[BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[TYPED:.*]] = fir.convert %[[FAST]] : (!fir.ref<i8>) -> !fir.ref<i32>
+ // CHECK: fir.call @use_i32(%[[TYPED]])
+ // CHECK: } else {
+ // CHECK: %[[FALLBACK:.*]] = fir.array_coor %[[A]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.call @use_i32(%[[FALLBACK]])
+ // CHECK: return
+
+ // A non-index constant one is already a unit step without a conversion.
+ func.func @direct_i8_static_one_step(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %step = arith.constant 1 : i8
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %step
+ : (index, index, i8) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @direct_i8_static_one_step(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK: fir.box_dims %[[A]],
+ // CHECK: fir.if
+ // CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+ // CHECK: %[[FAST:.*]] = fir.coordinate_of %[[BYTES]],
+ // CHECK: %[[TYPED:.*]] = fir.convert %[[FAST]] : (!fir.ref<i8>) -> !fir.ref<i32>
+ // CHECK: fir.call @use_i32(%[[TYPED]])
+ // CHECK: } else {
+ // CHECK: %[[FALLBACK:.*]] = fir.array_coor %[[A]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.call @use_i32(%[[FALLBACK]])
+ // CHECK: return
+
+ // Signedness after a truncation determines how the resulting one is widened.
+ func.func @unsigned_after_trunc_static_one_step(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c257 = arith.constant 257 : i16
+ %step = fir.convert %c257 : (i16) -> ui8
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %step
+ : (index, index, ui8) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @unsigned_after_trunc_static_one_step(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK: fir.box_dims %[[A]],
+ // CHECK: fir.if
+ // CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+ // CHECK: %[[FAST:.*]] = fir.coordinate_of %[[BYTES]],
+ // CHECK: %[[TYPED:.*]] = fir.convert %[[FAST]] : (!fir.ref<i8>) -> !fir.ref<i32>
+ // CHECK: fir.call @use_i32(%[[TYPED]])
+ // CHECK: } else {
+ // CHECK: %[[FALLBACK:.*]] = fir.array_coor %[[A]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.call @use_i32(%[[FALLBACK]])
+ // CHECK: return
+
+ // Preserve every conversion in a chain whose intermediate value is index.
+ func.func @index_middle_static_one_step(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c257 = arith.constant 257 : i16
+ %narrow = fir.convert %c257 : (i16) -> i8
+ %asIndex = fir.convert %narrow : (i8) -> index
+ %step = fir.convert %asIndex : (index) -> i64
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %step
+ : (index, index, i64) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @index_middle_static_one_step(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK: fir.box_dims %[[A]],
+ // CHECK: fir.if
+ // CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+ // CHECK: %[[FAST:.*]] = fir.coordinate_of %[[BYTES]],
+ // CHECK: %[[TYPED:.*]] = fir.convert %[[FAST]] : (!fir.ref<i8>) -> !fir.ref<i32>
+ // CHECK: fir.call @use_i32(%[[TYPED]])
+ // CHECK: } else {
+ // CHECK: %[[FALLBACK:.*]] = fir.array_coor %[[A]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.call @use_i32(%[[FALLBACK]])
+ // CHECK: return
+
+ // Generic XArrayCoor truncates a wider step to its 64-bit address index.
+ func.func @wide_static_one_step(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %wide = arith.constant 18446744073709551617 : i128
+ %middle = fir.convert %wide : (i128) -> i96
+ %narrow = fir.convert %middle : (i96) -> i32
+ %step = fir.convert %narrow : (i32) -> i128
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %step
+ : (index, index, i128) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @wide_static_one_step(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK: fir.box_dims %[[A]],
+ // CHECK: fir.if
+ // CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+ // CHECK: %[[FAST:.*]] = fir.coordinate_of %[[BYTES]],
+ // CHECK: %[[TYPED:.*]] = fir.convert %[[FAST]] : (!fir.ref<i8>) -> !fir.ref<i32>
+ // CHECK: fir.call @use_i32(%[[TYPED]])
+ // CHECK: } else {
+ // CHECK: %[[FALLBACK:.*]] = fir.array_coor %[[A]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.call @use_i32(%[[FALLBACK]])
+ // CHECK: return
+
+ // A conversion from a non-integer value is not an integer bit-cast chain.
+ func.func @noninteger_static_one_step_rejected(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %one = arith.constant 1.0 : f32
+ %step = fir.convert %one : (f32) -> index
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %step
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @noninteger_static_one_step_rejected(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: %[[ADDRESS:.*]] = fir.array_coor %[[A]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.call @use_i32(%[[ADDRESS]])
+ // CHECK: return
+
+ // A kind mapped to zero bits cannot participate in address arithmetic.
+ func.func @zero_kind_width_static_one_step_rejected(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1_i32 = arith.constant 1 : i32
+ %kindZero = fir.convert %c1_i32 : (i32) -> !fir.int<3>
+ %step = fir.convert %kindZero : (!fir.int<3>) -> index
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %step
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @zero_kind_width_static_one_step_rejected(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: %[[ADDRESS:.*]] = fir.array_coor %[[A]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.call @use_i32(%[[ADDRESS]])
+ // CHECK: return
+
+ // Mixed owners are outside the initial slice scope. The independent
+ // flat descriptor keeps the slice-free path, while the slice remains
+ // generic.
+ func.func @slice_with_independent_flat_uses_slice_free_path(
+ %slicedArg: !fir.box<!fir.array<?xi32>>,
+ %flatArg: !fir.box<!fir.array<?x?xi32>>, %outer: index) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %flat = fir.array_coor %flatArg %i, %outer
+ : (!fir.box<!fir.array<?x?xi32>>, index, index) -> !fir.ref<i32>
+ %sliced = fir.array_coor %slicedArg [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%sliced) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%flat) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @slice_with_independent_flat_uses_slice_free_path(
+ // CHECK-SAME: %[[SLICED:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[FLAT:[^:]+]]: !fir.box
+ // CHECK-NOT: fir.box_dims %[[SLICED]],
+ // CHECK: fir.box_dims %[[FLAT]],
+ // CHECK-NOT: fir.box_dims %[[SLICED]],
+ // CHECK: fir.if
+ // CHECK: %[[FLAT_TYPED:.*]] = fir.box_addr
+ // CHECK-SAME: !fir.ref<!fir.array<?xi32>>
+ // CHECK-NOT: !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop
+ // CHECK: arith.muli
+ // CHECK: arith.shrsi
+ // CHECK: arith.addi
+ // CHECK: %[[FLAT_FAST:.*]] = fir.coordinate_of %[[FLAT_TYPED]],
+ // CHECK: %[[SLICED_FAST:.*]] = fir.array_coor %[[SLICED]]{{.*}}[%{{.*}}]
+ // CHECK: fir.call @use_i32(%[[SLICED_FAST]])
+ // CHECK: fir.call @use_i32(%[[FLAT_FAST]])
+ // CHECK: } else {
+ // CHECK: %[[FLAT_SLOW:.*]] = fir.array_coor %[[FLAT]]
+ // CHECK: %[[SLICED_SLOW:.*]] = fir.array_coor %[[SLICED]]{{.*}}[%{{.*}}]
+ // CHECK: fir.call @use_i32(%[[SLICED_SLOW]])
+ // CHECK: fir.call @use_i32(%[[FLAT_SLOW]])
+
+ // DISABLED-LABEL: func.func @slice_with_independent_flat_uses_slice_free_path(
+ // DISABLED-SAME: %[[SLICED:[^:]+]]: !fir.box
+ // DISABLED-SAME: %[[FLAT:[^:]+]]: !fir.box
+ // DISABLED-NOT: fir.box_dims %[[SLICED]],
+ // DISABLED: fir.box_dims %[[FLAT]],
+ // DISABLED-NOT: fir.box_dims %[[SLICED]],
+ // DISABLED: fir.if
+ // DISABLED: %[[FLAT_TYPED:.*]] = fir.box_addr
+ // DISABLED: fir.do_loop
+ // DISABLED: %[[FLAT_FAST:.*]] = fir.coordinate_of %[[FLAT_TYPED]],
+ // DISABLED: %[[SLICED_FAST:.*]] = fir.array_coor %[[SLICED]]
+ // DISABLED: fir.call @use_i32(%[[SLICED_FAST]])
+ // DISABLED: fir.call @use_i32(%[[FLAT_FAST]])
+ // DISABLED: } else {
+ // DISABLED: %[[FLAT_SLOW:.*]] = fir.array_coor %[[FLAT]]
+ // DISABLED: %[[SLICED_SLOW:.*]] = fir.array_coor %[[SLICED]]
+ // DISABLED: fir.call @use_i32(%[[SLICED_SLOW]])
+ // DISABLED: fir.call @use_i32(%[[FLAT_SLOW]])
+ // DISABLED: return
+
+ // A provisional slice-free candidate may be removed after an inner sliced
+ // use rejects that descriptor. Preserve an independent sliced descriptor
+ // until final owner selection instead of losing both transformations.
+ func.func @late_slice_free_rejection_preserves_slice(
+ %slicedArg: !fir.box<!fir.array<?xi32>>,
+ %conflictedArg: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slicedSection = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ %conflictedSection = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %sliced = fir.array_coor %slicedArg [%slicedSection] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %flat = fir.array_coor %conflictedArg %i
+ : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+ fir.call @use_i32(%sliced) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%flat) : (!fir.ref<i32>) -> ()
+ fir.do_loop %j = %c1 to %c8 step %c1 {
+ %conflicted = fir.array_coor %conflictedArg [%conflictedSection] %j
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%conflicted) : (!fir.ref<i32>) -> ()
+ }
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @late_slice_free_rejection_preserves_slice(
+ // CHECK-SAME: %[[SLICED:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[CONFLICTED:[^:]+]]: !fir.box
+ // CHECK: %[[SLICED_SECTION:.*]] = fir.slice
+ // CHECK: %[[CONFLICTED_SECTION:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[CONFLICTED]],
+ // CHECK: fir.box_dims %[[SLICED]],
+ // CHECK-NOT: fir.box_dims %[[CONFLICTED]],
+ // CHECK: fir.if
+ // CHECK: %[[SLICED_BYTE_BOX:.*]] = fir.convert %[[SLICED]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[SLICED_BYTES:.*]] = fir.box_addr %[[SLICED_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.coordinate_of %[[SLICED_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[FAST_FLAT:.*]] = fir.array_coor %[[CONFLICTED]]
+ // CHECK-NOT: fir.box_dims %[[CONFLICTED]],
+ // CHECK-NOT: fir.if
+ // CHECK-NOT: fir.coordinate_of
+ // CHECK: %[[FAST_SLICED:.*]] = fir.array_coor %[[CONFLICTED]]
+ // CHECK-SAME: [%[[CONFLICTED_SECTION]]]
+ // CHECK: } else {
+ // CHECK: fir.array_coor %[[SLICED]] [%[[SLICED_SECTION]]]
+ // CHECK: %[[SLOW_FLAT:.*]] = fir.array_coor %[[CONFLICTED]]
+ // CHECK-NOT: fir.box_dims %[[CONFLICTED]],
+ // CHECK-NOT: fir.if
+ // CHECK-NOT: fir.coordinate_of
+ // CHECK: %[[SLOW_SLICED:.*]] = fir.array_coor %[[CONFLICTED]]
+ // CHECK-SAME: [%[[CONFLICTED_SECTION]]]
+ // CHECK-NOT: fir.box_dims %[[CONFLICTED]],
+ // CHECK: return
+
+ // Separate sibling owners can share the same descriptors without cloning
+ // one another. Both loops return an accumulation, and the first result
+ // initializes the second loop, as in sequential reduction owners.
+ func.func @independent_owners_supported(
+ %a: !fir.box<!fir.array<?xi32>>,
+ %b: !fir.box<!fir.array<?xi32>>) -> i32 {
+ %c0 = arith.constant 0 : i32
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %sliceA = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ %sliceB = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ %first = fir.do_loop %i = %c1 to %c8 step %c1
+ iter_args(%sum0 = %c0) -> (i32) {
+ %x = fir.array_coor %a [%sliceA] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %y = fir.array_coor %b [%sliceB] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %xValue = fir.load %x : !fir.ref<i32>
+ %yValue = fir.load %y : !fir.ref<i32>
+ %partial0 = arith.addi %sum0, %xValue : i32
+ %next0 = arith.addi %partial0, %yValue : i32
+ fir.result %next0 : i32
+ }
+ %second = fir.do_loop %j = %c1 to %c8 step %c1
+ iter_args(%sum1 = %first) -> (i32) {
+ %x = fir.array_coor %a [%sliceA] %j
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %y = fir.array_coor %b [%sliceB] %j
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %xValue = fir.load %x : !fir.ref<i32>
+ %yValue = fir.load %y : !fir.ref<i32>
+ %partial1 = arith.addi %sum1, %xValue : i32
+ %next1 = arith.addi %partial1, %yValue : i32
+ fir.result %next1 : i32
+ }
+ return %second : i32
+ }
+ // CHECK-LABEL: func.func @independent_owners_supported(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[B:[^:]+]]: !fir.box
+ // CHECK: %[[ZERO:[^ ]+]] = arith.constant 0 : i32
+ // CHECK: %[[SLICE_A:[^ ]+]] = fir.slice
+ // CHECK: %[[SLICE_B:[^ ]+]] = fir.slice
+ // CHECK: fir.box_dims %[[A]],
+ // CHECK: fir.box_dims %[[B]],
+ // CHECK: %[[FIRST_IF:[^ ]+]] = fir.if {{.*}} -> (i32) {
+ // CHECK: %[[FIRST_A_BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[FIRST_A_BYTES:.*]] = fir.box_addr %[[FIRST_A_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[FIRST_B_BYTE_BOX:.*]] = fir.convert %[[B]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[FIRST_B_BYTES:.*]] = fir.box_addr %[[FIRST_B_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[FIRST_FAST:[^ ]+]] = fir.do_loop {{.*}} iter_args(%{{[^ ]+}} = %[[ZERO]]) -> (i32) {
+ // CHECK: fir.coordinate_of %[[FIRST_A_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: fir.coordinate_of %[[FIRST_B_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: fir.result %{{[^ ]+}} : i32
+ // CHECK: }
+ // CHECK: fir.result %[[FIRST_FAST]] : i32
+ // CHECK: } else {
+ // CHECK: %[[FIRST_SLOW:[^ ]+]] = fir.do_loop {{.*}} iter_args(%{{[^ ]+}} = %[[ZERO]]) -> (i32) {
+ // CHECK: fir.array_coor %[[A]]{{.*}}%[[SLICE_A]]
+ // CHECK: fir.array_coor %[[B]]{{.*}}%[[SLICE_B]]
+ // CHECK: fir.result %{{[^ ]+}} : i32
+ // CHECK: }
+ // CHECK: fir.result %[[FIRST_SLOW]] : i32
+ // CHECK: }
+ // CHECK: fir.box_dims %[[A]],
+ // CHECK: fir.box_dims %[[B]],
+ // CHECK: %[[SECOND_IF:[^ ]+]] = fir.if {{.*}} -> (i32) {
+ // CHECK: %[[SECOND_A_BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[SECOND_A_BYTES:.*]] = fir.box_addr %[[SECOND_A_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[SECOND_B_BYTE_BOX:.*]] = fir.convert %[[B]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[SECOND_B_BYTES:.*]] = fir.box_addr %[[SECOND_B_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[SECOND_FAST:[^ ]+]] = fir.do_loop {{.*}} iter_args(%{{[^ ]+}} = %[[FIRST_IF]]) -> (i32) {
+ // CHECK: fir.coordinate_of %[[SECOND_A_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: fir.coordinate_of %[[SECOND_B_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: fir.result %{{[^ ]+}} : i32
+ // CHECK: }
+ // CHECK: fir.result %[[SECOND_FAST]] : i32
+ // CHECK: } else {
+ // CHECK: %[[SECOND_SLOW:[^ ]+]] = fir.do_loop {{.*}} iter_args(%{{[^ ]+}} = %[[FIRST_IF]]) -> (i32) {
+ // CHECK: fir.array_coor %[[A]]{{.*}}%[[SLICE_A]]
+ // CHECK: fir.array_coor %[[B]]{{.*}}%[[SLICE_B]]
+ // CHECK: fir.result %{{[^ ]+}} : i32
+ // CHECK: }
+ // CHECK: fir.result %[[SECOND_SLOW]] : i32
+ // CHECK: }
+ // CHECK: return %[[SECOND_IF]] : i32
+
+ // Sibling owners still require one descriptor-wide decision. An
+ // unsupported owner keeps every use of that descriptor on the generic path.
+ func.func @independent_owner_rejection_is_atomic(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c8 = arith.constant 8 : index
+ %bad = fir.slice %c1, %c8, %c2
+ : (index, index, index) -> !fir.slice<1>
+ %good = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %x = fir.array_coor %a [%bad] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%x) : (!fir.ref<i32>) -> ()
+ }
+ fir.do_loop %j = %c1 to %c8 step %c1 {
+ %y = fir.array_coor %a [%good] %j
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%y) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @independent_owner_rejection_is_atomic(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[BAD:.*]] = fir.slice
+ // CHECK: %[[GOOD:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop
+ // CHECK: %[[BAD_ADDRESS:.*]] = fir.array_coor %[[A]] [%[[BAD]]]
+ // CHECK: fir.call @use_i32(%[[BAD_ADDRESS]])
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop
+ // CHECK: %[[GOOD_ADDRESS:.*]] = fir.array_coor %[[A]] [%[[GOOD]]]
+ // CHECK: fir.call @use_i32(%[[GOOD_ADDRESS]])
+ // CHECK-NOT: fir.if
+ // CHECK: return
+
+ // Descriptor-wide rejection is independent of owner discovery order. A
+ // supported owner encountered first must remain generic when a later owner
+ // of the same descriptor has an unsupported section step.
+ func.func @independent_owner_late_rejection_is_atomic(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c8 = arith.constant 8 : index
+ %good = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ %bad = fir.slice %c1, %c8, %c2
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %x = fir.array_coor %a [%good] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%x) : (!fir.ref<i32>) -> ()
+ }
+ fir.do_loop %j = %c1 to %c8 step %c1 {
+ %y = fir.array_coor %a [%bad] %j
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%y) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @independent_owner_late_rejection_is_atomic(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[GOOD:.*]] = fir.slice
+ // CHECK: %[[BAD:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop %[[I:[^ ]+]] =
+ // CHECK: %[[GOOD_ADDRESS:.*]] = fir.array_coor %[[A]] [%[[GOOD]]] %[[I]]
+ // CHECK: fir.call @use_i32(%[[GOOD_ADDRESS]])
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop %[[J:[^ ]+]] =
+ // CHECK: %[[BAD_ADDRESS:.*]] = fir.array_coor %[[A]] [%[[BAD]]] %[[J]]
+ // CHECK: fir.call @use_i32(%[[BAD_ADDRESS]])
+ // CHECK-NOT: fir.if
+ // CHECK: return
+
+ // Independent nested owners are transformed in post-order. The inner
+ // versioning is therefore already present when the outer owner is cloned and
+ // must survive in both versions of that outer loop.
+ func.func @nested_independent_owners_supported(
+ %outer: !fir.box<!fir.array<?xi32>>,
+ %inner: !fir.box<!fir.array<?xf64>>, %innerLower: index) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %outerSlice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %a = fir.array_coor %outer [%outerSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%a) : (!fir.ref<i32>) -> ()
+ %innerSlice = fir.slice %innerLower, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %j = %c1 to %c8 step %c1 {
+ %b = fir.array_coor %inner [%innerSlice] %j
+ : (!fir.box<!fir.array<?xf64>>, !fir.slice<1>, index)
+ -> !fir.ref<f64>
+ fir.call @use_f64(%b) : (!fir.ref<f64>) -> ()
+ }
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @nested_independent_owners_supported(
+ // CHECK-SAME: %[[OUTER:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[INNER:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[INNER_LOWER:[^:]+]]: index
+ // CHECK: %[[OUTER_SLICE:.*]] = fir.slice
+ // CHECK: %[[OUTER_D0:.*]]:3 = fir.box_dims %[[OUTER]],
+ // CHECK: %[[OUTER_SIZE:.*]] = arith.constant 4 : index
+ // CHECK: %[[OUTER_PRED:.*]] = arith.cmpi eq, %[[OUTER_D0]]#2, %[[OUTER_SIZE]]
+ // CHECK: fir.if %[[OUTER_PRED]]
+ // CHECK: %[[OUTER_BYTE_BOX:.*]] = fir.convert %[[OUTER]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[OUTER_BYTES:.*]] = fir.box_addr %[[OUTER_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop
+ // CHECK: %[[OUTER_FAST_ADDRESS:.*]] = fir.coordinate_of %[[OUTER_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[OUTER_FAST_REF:.*]] = fir.convert %[[OUTER_FAST_ADDRESS]]
+ // CHECK: fir.call @use_i32(%[[OUTER_FAST_REF]])
+ // CHECK: %[[INNER_SLICE_FAST:.*]] = fir.slice %[[INNER_LOWER]],
+ // CHECK: %[[INNER_FAST_D0:.*]]:3 = fir.box_dims %[[INNER]],
+ // CHECK: %[[INNER_FAST_SIZE:.*]] = arith.constant 8 : index
+ // CHECK: %[[INNER_FAST_PRED:.*]] = arith.cmpi eq, %[[INNER_FAST_D0]]#2, %[[INNER_FAST_SIZE]]
+ // CHECK: fir.if %[[INNER_FAST_PRED]]
+ // CHECK: %[[INNER_FAST_BYTE_BOX:.*]] = fir.convert %[[INNER]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[INNER_FAST_BYTES:.*]] = fir.box_addr %[[INNER_FAST_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop
+ // CHECK: %[[INNER_FAST_ADDRESS:.*]] = fir.coordinate_of %[[INNER_FAST_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[INNER_FAST_REF:.*]] = fir.convert %[[INNER_FAST_ADDRESS]]
+ // CHECK: fir.call @use_f64(%[[INNER_FAST_REF]])
+ // CHECK: } else {
+ // CHECK: %[[INNER_FAST_FALLBACK:.*]] = fir.array_coor %[[INNER]]{{.*}}[%[[INNER_SLICE_FAST]]]
+ // CHECK: fir.call @use_f64(%[[INNER_FAST_FALLBACK]])
+ // CHECK: } else {
+ // CHECK: fir.do_loop
+ // CHECK: %[[OUTER_FALLBACK:.*]] = fir.array_coor %[[OUTER]]{{.*}}[%[[OUTER_SLICE]]]
+ // CHECK: fir.call @use_i32(%[[OUTER_FALLBACK]])
+ // CHECK: %[[INNER_SLICE_SLOW:.*]] = fir.slice %[[INNER_LOWER]],
+ // CHECK: %[[INNER_SLOW_D0:.*]]:3 = fir.box_dims %[[INNER]],
+ // CHECK: %[[INNER_SLOW_SIZE:.*]] = arith.constant 8 : index
+ // CHECK: %[[INNER_SLOW_PRED:.*]] = arith.cmpi eq, %[[INNER_SLOW_D0]]#2, %[[INNER_SLOW_SIZE]]
+ // CHECK: fir.if %[[INNER_SLOW_PRED]]
+ // CHECK: %[[INNER_SLOW_BYTE_BOX:.*]] = fir.convert %[[INNER]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[INNER_SLOW_BYTES:.*]] = fir.box_addr %[[INNER_SLOW_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop
+ // CHECK: %[[INNER_SLOW_ADDRESS:.*]] = fir.coordinate_of %[[INNER_SLOW_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[INNER_SLOW_REF:.*]] = fir.convert %[[INNER_SLOW_ADDRESS]]
+ // CHECK: fir.call @use_f64(%[[INNER_SLOW_REF]])
+ // CHECK: } else {
+ // CHECK: %[[INNER_SLOW_FALLBACK:.*]] = fir.array_coor %[[INNER]]{{.*}}[%[[INNER_SLICE_SLOW]]]
+ // CHECK: fir.call @use_f64(%[[INNER_SLOW_FALLBACK]])
+ // DISABLED-LABEL: func.func @nested_independent_owners_supported(
+ // DISABLED-SAME: %[[OUTER:[^:]+]]: !fir.box
+ // DISABLED-SAME: %[[INNER:[^:]+]]: !fir.box
+ // DISABLED-NOT: fir.if
+ // DISABLED: %[[OUTER_ADDRESS:.*]] = fir.array_coor %[[OUTER]]
+ // DISABLED: fir.call @use_i32(%[[OUTER_ADDRESS]])
+ // DISABLED: %[[INNER_ADDRESS:.*]] = fir.array_coor %[[INNER]]
+ // DISABLED: fir.call @use_f64(%[[INNER_ADDRESS]])
+ // DISABLED-NOT: fir.array_coor
+ // DISABLED-NOT: fir.call @use_i32
+ // DISABLED-NOT: fir.call @use_f64
+ // DISABLED-NOT: fir.if
+ // DISABLED: return
+
+ // Ancestor and descendant owners of the same descriptor stay generic
+ // because cloning the ancestor would invalidate the descendant's plan.
+ func.func @nested_owners_rejected(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %x = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%x) : (!fir.ref<i32>) -> ()
+ fir.do_loop %j = %c1 to %c8 step %c1 {
+ %y = fir.array_coor %a [%slice] %j
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%y) : (!fir.ref<i32>) -> ()
+ }
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @nested_owners_rejected(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop
+ // CHECK: %[[OUTER_ADDRESS:.*]] = fir.array_coor %[[A]] [%[[SLICE]]]
+ // CHECK: fir.call @use_i32(%[[OUTER_ADDRESS]])
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop
+ // CHECK: %[[INNER_ADDRESS:.*]] = fir.array_coor %[[A]] [%[[SLICE]]]
+ // CHECK: fir.call @use_i32(%[[INNER_ADDRESS]])
+ // CHECK-NOT: fir.if
+ // CHECK: return
+
+ // Subtree intervals must remain transitive through a loop that has no use of
+ // the descriptor. The outer and innermost owners are still an
+ // ancestor/descendant pair and must therefore remain generic together.
+ func.func @three_level_owners_rejected(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %outer = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%outer) : (!fir.ref<i32>) -> ()
+ fir.do_loop %j = %c1 to %c8 step %c1 {
+ fir.do_loop %k = %c1 to %c8 step %c1 {
+ %inner = fir.array_coor %a [%slice] %k
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%inner) : (!fir.ref<i32>) -> ()
+ }
+ }
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @three_level_owners_rejected(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop
+ // CHECK: %[[OUTER:.*]] = fir.array_coor %[[A]] [%[[SLICE]]]
+ // CHECK: fir.call @use_i32(%[[OUTER]])
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop
+ // CHECK: %[[INNER:.*]] = fir.array_coor %[[A]] [%[[SLICE]]]
+ // CHECK: fir.call @use_i32(%[[INNER]])
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: return
+
+ // A flat owner nested above or below a sliced owner prevents slice
+ // plans in either ordering. Unrelated slice-free versioning remains
+ // permitted.
+ func.func @nested_slice_flat_rejected(
+ %outerSlice: !fir.box<!fir.array<?xi32>>,
+ %innerSlice: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %a = fir.array_coor %outerSlice [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %b = fir.array_coor %innerSlice %i
+ : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+ fir.call @use_i32(%a) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%b) : (!fir.ref<i32>) -> ()
+ fir.do_loop %j = %c1 to %c8 step %c1 {
+ %c = fir.array_coor %outerSlice %j
+ : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
+ %d = fir.array_coor %innerSlice [%slice] %j
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%c) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%d) : (!fir.ref<i32>) -> ()
+ }
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @nested_slice_flat_rejected(
+ // CHECK-SAME: %[[OUTER:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[INNER:[^:]+]]: !fir.box
+ // CHECK-NOT: !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.array_coor %[[OUTER]]{{.*}}[%{{.*}}]
+ // CHECK-NOT: !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.array_coor %[[INNER]]{{.*}}[%{{.*}}]
+ // CHECK-NOT: !fir.ref<!fir.array<?xi8>>
+ // CHECK: return
+
+ // A nested non-loop region remains part of the nearest enclosing loop's
+ // direct-use set. The exact access is found and rewritten inside the clone.
+ func.func @nested_non_loop_region_supported(
+ %a: !fir.box<!fir.array<?xi32>>, %take: i1) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ fir.if %take {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @nested_non_loop_region_supported(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[TAKE:[^:]+]]: i1
+ // CHECK: %[[D0:.*]]:3 = fir.box_dims %[[A]],
+ // CHECK: %[[PRED:.*]] = arith.cmpi eq, %[[D0]]#2,
+ // CHECK: fir.if %[[PRED]]
+ // CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop
+ // CHECK: fir.if %[[TAKE]]
+ // CHECK: %[[FAST_ADDRESS:.*]] = fir.coordinate_of %[[BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[FAST:.*]] = fir.convert %[[FAST_ADDRESS]]
+ // CHECK: fir.call @use_i32(%[[FAST]])
+ // CHECK: } else {
+ // CHECK: fir.do_loop
+ // CHECK: fir.if %[[TAKE]]
+ // CHECK: %[[FALLBACK:.*]] = fir.array_coor %[[A]]
+ // CHECK: fir.call @use_i32(%[[FALLBACK]])
+ // CHECK: return
+
+ // An unknown section step rejects only its descriptor. Static-unit
+ // descriptors discovered before and after it retain their byte fast paths.
+ func.func @dynamic_step_rejected(
+ %firstArg: !fir.box<!fir.array<?xi32>>,
+ %badArg: !fir.box<!fir.array<?xi32>>,
+ %goodArg: !fir.box<!fir.array<?xi32>>, %dynamicStep: index) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %badSlice = fir.slice %c1, %c8, %dynamicStep
+ : (index, index, index) -> !fir.slice<1>
+ %goodSlice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %first = fir.array_coor %firstArg [%goodSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %bad = fir.array_coor %badArg [%badSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %good = fir.array_coor %goodArg [%goodSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%first) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%bad) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%good) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @dynamic_step_rejected(
+ // CHECK-SAME: %[[FIRST:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[BAD:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[GOOD:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[STEP:[^:]+]]: index
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: fir.box_dims %[[FIRST]],
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: fir.box_dims %[[GOOD]],
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK-NOT: arith.cmpi eq, {{.*}}%[[STEP]]
+ // CHECK: fir.if
+ // CHECK: %[[FIRST_BYTE_BOX:.*]] = fir.convert %[[FIRST]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[FIRST_BYTES:.*]] = fir.box_addr %[[FIRST_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[GOOD_BYTE_BOX:.*]] = fir.convert %[[GOOD]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[FIRST_ADDRESS:.*]] = fir.coordinate_of %[[FIRST_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[FIRST_FAST:.*]] = fir.convert %[[FIRST_ADDRESS]]
+ // CHECK: %[[BAD_FAST:.*]] = fir.array_coor %[[BAD]]
+ // CHECK: %[[GOOD_ADDRESS:.*]] = fir.coordinate_of %[[GOOD_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[GOOD_FAST:.*]] = fir.convert %[[GOOD_ADDRESS]]
+ // CHECK: fir.call @use_i32(%[[FIRST_FAST]])
+ // CHECK: fir.call @use_i32(%[[BAD_FAST]])
+ // CHECK: fir.call @use_i32(%[[GOOD_FAST]])
+ // CHECK: } else {
+ // CHECK: %[[FIRST_FALLBACK:.*]] = fir.array_coor %[[FIRST]]
+ // CHECK: %[[BAD_FALLBACK:.*]] = fir.array_coor %[[BAD]]
+ // CHECK: %[[GOOD_FALLBACK:.*]] = fir.array_coor %[[GOOD]]
+ // CHECK: fir.call @use_i32(%[[FIRST_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[BAD_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[GOOD_FALLBACK]])
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: return
+
+ // This patch consumes the canonical frontend rebox(declare(pack(arg)))
+ // order. The alternative declare(rebox(arg)) order is an explicit scope
+ // ceiling, not a preferred long-term normalization rule.
+ func.func @wrapper_order_scope_ceiling(
+ %goodArg: !fir.box<!fir.array<?xi32>>,
+ %boundaryArg: !fir.box<!fir.array<?xi32>>) {
+ %packed = fir.pack_array %goodArg heap whole
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %declared = fir.declare %packed {uniq_name = "supported"}
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %reboxed = fir.rebox %declared
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %boundaryRebox = fir.rebox %boundaryArg
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %boundaryDeclared = fir.declare %boundaryRebox {uniq_name = "boundary"}
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %supported = fir.array_coor %reboxed [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %boundary = fir.array_coor %boundaryDeclared [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%supported) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%boundary) : (!fir.ref<i32>) -> ()
+ }
+ fir.unpack_array %packed to %goodArg heap
+ : !fir.box<!fir.array<?xi32>>
+ return
+ }
+ // CHECK-LABEL: func.func @wrapper_order_scope_ceiling(
+ // CHECK-SAME: %[[GOOD_ARG:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[BOUNDARY_ARG:[^:]+]]: !fir.box
+ // CHECK: %[[PACKED:.*]] = fir.pack_array %[[GOOD_ARG]] heap whole
+ // CHECK: %[[DECLARED:.*]] = fir.declare %[[PACKED]]
+ // CHECK: %[[REBOXED:.*]] = fir.rebox %[[DECLARED]]
+ // CHECK: %[[BOUNDARY_REBOX:.*]] = fir.rebox %[[BOUNDARY_ARG]]
+ // CHECK: %[[BOUNDARY_DECLARED:.*]] = fir.declare %[[BOUNDARY_REBOX]]
+ // CHECK-NOT: fir.box_dims %[[BOUNDARY_DECLARED]],
+ // CHECK: fir.box_dims %[[REBOXED]],
+ // CHECK-NOT: fir.box_dims %[[BOUNDARY_DECLARED]],
+ // CHECK: fir.if
+ // CHECK: %[[SUPPORTED_BOX:.*]] = fir.convert %[[REBOXED]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[SUPPORTED_BYTES:.*]] = fir.box_addr %[[SUPPORTED_BOX]]
+ // CHECK: %[[SUPPORTED_ADDRESS:.*]] = fir.coordinate_of %[[SUPPORTED_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[SUPPORTED_FAST:.*]] = fir.convert %[[SUPPORTED_ADDRESS]]
+ // CHECK: %[[BOUNDARY_FAST:.*]] = fir.array_coor %[[BOUNDARY_DECLARED]]
+ // CHECK: fir.call @use_i32(%[[SUPPORTED_FAST]])
+ // CHECK: fir.call @use_i32(%[[BOUNDARY_FAST]])
+ // CHECK: } else {
+ // CHECK: %[[SUPPORTED_FALLBACK:.*]] = fir.array_coor %[[REBOXED]]
+ // CHECK: %[[BOUNDARY_FALLBACK:.*]] = fir.array_coor %[[BOUNDARY_DECLARED]]
+ // CHECK: fir.call @use_i32(%[[SUPPORTED_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[BOUNDARY_FALLBACK]])
+ // CHECK: fir.unpack_array %[[PACKED]] to %[[GOOD_ARG]] heap
+ // CHECK-NOT: fir.box_dims %[[BOUNDARY_DECLARED]],
+ // CHECK: return
+
+ // Distinct concrete wrappers remain distinct descriptor groups even when
+ // normalization traces both of them to the same function argument.
+ func.func @wrapper_identity_is_preserved(
+ %arg: !fir.box<!fir.array<?xi32>>) {
+ %declared = fir.declare %arg {uniq_name = "declared"}
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %reboxed = fir.rebox %declared
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %declaredAddress = fir.array_coor %declared [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %reboxedAddress = fir.array_coor %reboxed [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%declaredAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%reboxedAddress) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @wrapper_identity_is_preserved(
+ // CHECK-SAME: %[[ARG:[^:]+]]: !fir.box
+ // CHECK: %[[DECLARED:.*]] = fir.declare %[[ARG]]
+ // CHECK: %[[REBOXED:.*]] = fir.rebox %[[DECLARED]]
+ // CHECK: fir.box_dims %[[DECLARED]],
+ // CHECK: fir.box_dims %[[REBOXED]],
+ // CHECK: fir.if
+ // CHECK: %[[DECLARED_BYTE_BOX:.*]] = fir.convert %[[DECLARED]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[DECLARED_BYTES:.*]] = fir.box_addr %[[DECLARED_BYTE_BOX]]
+ // CHECK: %[[REBOXED_BYTE_BOX:.*]] = fir.convert %[[REBOXED]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[REBOXED_BYTES:.*]] = fir.box_addr %[[REBOXED_BYTE_BOX]]
+ // CHECK: %[[DECLARED_BYTE_ADDRESS:.*]] = fir.coordinate_of
+ // CHECK-SAME: %[[DECLARED_BYTES]],
+ // CHECK: %[[DECLARED_FAST:.*]] = fir.convert %[[DECLARED_BYTE_ADDRESS]]
+ // CHECK: %[[REBOXED_BYTE_ADDRESS:.*]] = fir.coordinate_of
+ // CHECK-SAME: %[[REBOXED_BYTES]],
+ // CHECK: %[[REBOXED_FAST:.*]] = fir.convert %[[REBOXED_BYTE_ADDRESS]]
+ // CHECK: fir.call @use_i32(%[[DECLARED_FAST]])
+ // CHECK: fir.call @use_i32(%[[REBOXED_FAST]])
+ // CHECK: } else {
+ // CHECK: %[[DECLARED_FALLBACK:.*]] = fir.array_coor %[[DECLARED]]
+ // CHECK: %[[REBOXED_FALLBACK:.*]] = fir.array_coor %[[REBOXED]]
+ // CHECK: fir.call @use_i32(%[[DECLARED_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[REBOXED_FALLBACK]])
+ // CHECK: return
+
+ // A rebox with a non-unit section step may produce a non-contiguous view.
+ // Descriptor normalization must stop at that physical descriptor.
+ func.func @noncontiguous_rebox_rejected(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c2 = arith.constant 2 : index
+ %c8 = arith.constant 8 : index
+ %noncontiguous = fir.slice %c1, %c8, %c2
+ : (index, index, index) -> !fir.slice<1>
+ %reboxed = fir.rebox %a [%noncontiguous]
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>)
+ -> !fir.box<!fir.array<?xi32>>
+ %direct = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %reboxed [%direct] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @noncontiguous_rebox_rejected(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[NONCONTIGUOUS:.*]] = fir.slice
+ // CHECK: %[[REBOXED:.*]] = fir.rebox %[[A]] [%[[NONCONTIGUOUS]]]
+ // CHECK: %[[DIRECT:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.box_dims %[[REBOXED]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop
+ // CHECK: %[[ADDRESS:.*]] = fir.array_coor %[[REBOXED]] {{.*}}[%[[DIRECT]]]
+ // CHECK: fir.call @use_i32(%[[ADDRESS]])
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.box_dims %[[REBOXED]],
+ // CHECK-NOT: fir.if
+ // CHECK: return
+
+ // Unsafe slice operands and a mismatched result type fail closed without
+ // disabling an independent descriptor in the same owner.
+ func.func @fail_closed_slice_forms(
+ %badTriple: !fir.box<!fir.array<?xi32>>,
+ %badLower: !fir.box<!fir.array<?xi32>>,
+ %badResult: !fir.box<!fir.array<?xf32>>,
+ %badCharacter: !fir.ref<!fir.array<?x!fir.char<1,?>>>,
+ %badRecord: !fir.box<!fir.array<?x!fir.type<slice_element{i:i32}>>>,
+ %good: !fir.box<!fir.array<?xi32>>, %narrowLower: ui32, %len: index) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %undef = fir.undefined index
+ %unsupported = fir.slice %c1, %c8, %undef
+ : (index, index, index) -> !fir.slice<1>
+ %narrowUnsigned = fir.slice %narrowLower, %c8, %c1
+ : (ui32, index, index) -> !fir.slice<1>
+ %unit = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %tripleAddress = fir.array_coor %badTriple [%unsupported] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %lowerAddress = fir.array_coor %badLower [%narrowUnsigned] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %resultAddress = fir.array_coor %badResult [%unit] %i
+ : (!fir.box<!fir.array<?xf32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %characterAddress = fir.array_coor %badCharacter [%unit] %i typeparams %len
+ : (!fir.ref<!fir.array<?x!fir.char<1,?>>>, !fir.slice<1>, index,
+ index) -> !fir.ref<!fir.char<1,?>>
+ %recordAddress = fir.array_coor %badRecord [%unit] %i
+ : (!fir.box<!fir.array<?x!fir.type<slice_element{i:i32}>>>,
+ !fir.slice<1>, index) -> !fir.ref<!fir.type<slice_element{i:i32}>>
+ %goodAddress = fir.array_coor %good [%unit] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%tripleAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%lowerAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%resultAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_char(%characterAddress)
+ : (!fir.ref<!fir.char<1,?>>) -> ()
+ fir.call @use_record(%recordAddress)
+ : (!fir.ref<!fir.type<slice_element{i:i32}>>) -> ()
+ fir.call @use_i32(%goodAddress) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @fail_closed_slice_forms(
+ // CHECK-SAME: %[[BAD_TRIPLE:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[BAD_LOWER:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[BAD_RESULT:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[BAD_CHARACTER:[^:]+]]: !fir.ref
+ // CHECK-SAME: %[[BAD_RECORD:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[GOOD:[^:]+]]: !fir.box
+ // CHECK: %[[BAD_TRIPLE_SLICE:.*]] = fir.slice
+ // CHECK: %[[NARROW_UNSIGNED:.*]] = fir.slice
+ // CHECK: %[[UNIT:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[BAD_TRIPLE]],
+ // CHECK-NOT: fir.box_dims %[[BAD_LOWER]],
+ // CHECK-NOT: fir.box_dims %[[BAD_RESULT]],
+ // CHECK-NOT: fir.box_dims %[[BAD_CHARACTER]],
+ // CHECK-NOT: fir.box_dims %[[BAD_RECORD]],
+ // CHECK: fir.box_dims %[[GOOD]],
+ // CHECK-NOT: fir.box_dims %[[BAD_TRIPLE]],
+ // CHECK-NOT: fir.box_dims %[[BAD_LOWER]],
+ // CHECK-NOT: fir.box_dims %[[BAD_RESULT]],
+ // CHECK-NOT: fir.box_dims %[[BAD_CHARACTER]],
+ // CHECK-NOT: fir.box_dims %[[BAD_RECORD]],
+ // CHECK: fir.if
+ // CHECK: %[[GOOD_BYTE_BOX:.*]] = fir.convert %[[GOOD]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.array_coor %[[BAD_TRIPLE]] [%[[BAD_TRIPLE_SLICE]]]
+ // CHECK: fir.array_coor %[[BAD_LOWER]] [%[[NARROW_UNSIGNED]]]
+ // CHECK: fir.array_coor %[[BAD_RESULT]] [%[[UNIT]]]
+ // CHECK: fir.array_coor %[[BAD_CHARACTER]] [%[[UNIT]]]
+ // CHECK: fir.array_coor %[[BAD_RECORD]] [%[[UNIT]]]
+ // CHECK: fir.coordinate_of %[[GOOD_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: } else {
+ // CHECK: fir.array_coor %[[BAD_TRIPLE]] [%[[BAD_TRIPLE_SLICE]]]
+ // CHECK: fir.array_coor %[[BAD_LOWER]] [%[[NARROW_UNSIGNED]]]
+ // CHECK: fir.array_coor %[[BAD_RESULT]] [%[[UNIT]]]
+ // CHECK: fir.array_coor %[[BAD_CHARACTER]] [%[[UNIT]]]
+ // CHECK: fir.array_coor %[[BAD_RECORD]] [%[[UNIT]]]
+ // CHECK: fir.array_coor %[[GOOD]] [%[[UNIT]]]
+ // CHECK: return
+
+ // A component path is outside the direct-sequence byte formula. Keep that
+ // descriptor generic while independently versioning a supported descriptor
+ // in the same owner. The FIR verifier does not tie a slice field path to the
+ // memref element type, so this fixture reaches the path gate directly.
+ func.func @component_path_rejected(
+ %component: !fir.box<!fir.array<?xi32>>,
+ %good: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %field = fir.field_index j, !fir.type<t{i:i32,j:i32}>
+ %componentSlice = fir.slice %c1, %c8, %c1 path %field
+ : (index, index, index, !fir.field) -> !fir.slice<1>
+ %goodSlice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %componentAddress = fir.array_coor %component [%componentSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %goodAddress = fir.array_coor %good [%goodSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%componentAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%goodAddress) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @component_path_rejected(
+ // CHECK-SAME: %[[COMPONENT:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[GOOD:[^:]+]]: !fir.box
+ // CHECK: %[[COMPONENT_SLICE:.*]] = fir.slice
+ // CHECK: %[[GOOD_SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[COMPONENT]],
+ // CHECK: fir.box_dims %[[GOOD]],
+ // CHECK-NOT: fir.box_dims %[[COMPONENT]],
+ // CHECK: fir.if
+ // CHECK: %[[GOOD_BYTE_BOX:.*]] = fir.convert %[[GOOD]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[COMPONENT_FAST:.*]] = fir.array_coor %[[COMPONENT]] [%[[COMPONENT_SLICE]]]
+ // CHECK: %[[GOOD_FAST_ADDRESS:.*]] = fir.coordinate_of %[[GOOD_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[GOOD_FAST:.*]] = fir.convert %[[GOOD_FAST_ADDRESS]]
+ // CHECK: fir.call @use_i32(%[[COMPONENT_FAST]])
+ // CHECK: fir.call @use_i32(%[[GOOD_FAST]])
+ // CHECK: } else {
+ // CHECK: %[[COMPONENT_FALLBACK:.*]] = fir.array_coor %[[COMPONENT]] [%[[COMPONENT_SLICE]]]
+ // CHECK: %[[GOOD_FALLBACK:.*]] = fir.array_coor %[[GOOD]] [%[[GOOD_SLICE]]]
+ // CHECK: fir.call @use_i32(%[[COMPONENT_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[GOOD_FALLBACK]])
+ // CHECK-NOT: fir.box_dims %[[COMPONENT]],
+ // CHECK: return
+
+ // An unsupported triple in a trailing dimension must reject its descriptor
+ // independently of the leading-Section rule. Otherwise the emitter could
+ // misclassify it as Scalar and omit the generic-path step arithmetic.
+ func.func @trailing_unsupported_slice_triple(
+ %bad: !fir.box<!fir.array<?x?xi32>>,
+ %good: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c4 = arith.constant 4 : index
+ %c8 = arith.constant 8 : index
+ %undef = fir.undefined index
+ %badSlice = fir.slice %c1, %c8, %c1,
+ %undef, %c8, %c4
+ : (index, index, index, index, index, index) -> !fir.slice<2>
+ %goodSlice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %badAddress = fir.array_coor %bad [%badSlice] %i, %i
+ : (!fir.box<!fir.array<?x?xi32>>, !fir.slice<2>, index, index)
+ -> !fir.ref<i32>
+ %goodAddress = fir.array_coor %good [%goodSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%badAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%goodAddress) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @trailing_unsupported_slice_triple(
+ // CHECK-SAME: %[[BAD:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[GOOD:[^:]+]]: !fir.box
+ // CHECK: %[[BAD_SLICE:.*]] = fir.slice
+ // CHECK: %[[GOOD_SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: fir.box_dims %[[GOOD]],
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: fir.if
+ // CHECK: %[[GOOD_BYTE_BOX:.*]] = fir.convert %[[GOOD]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[BAD_FAST:.*]] = fir.array_coor %[[BAD]] [%[[BAD_SLICE]]]
+ // CHECK: %[[GOOD_FAST_ADDRESS:.*]] = fir.coordinate_of %[[GOOD_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[GOOD_FAST:.*]] = fir.convert %[[GOOD_FAST_ADDRESS]]
+ // CHECK: fir.call @use_i32(%[[BAD_FAST]])
+ // CHECK: fir.call @use_i32(%[[GOOD_FAST]])
+ // CHECK: } else {
+ // CHECK: %[[BAD_FALLBACK:.*]] = fir.array_coor %[[BAD]] [%[[BAD_SLICE]]]
+ // CHECK: %[[GOOD_FALLBACK:.*]] = fir.array_coor %[[GOOD]] [%[[GOOD_SLICE]]]
+ // CHECK: fir.call @use_i32(%[[BAD_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[GOOD_FALLBACK]])
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: return
+
+ // A sliced access with fewer coordinates than the descriptor rank remains
+ // generic without disabling an independent full-rank access in the owner.
+ func.func @reduced_rank_rejected(
+ %bad: !fir.box<!fir.array<?x?xi32>>,
+ %good: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %undef = fir.undefined index
+ %badSlice = fir.slice %c1, %c8, %c1,
+ %c1, %undef, %undef
+ : (index, index, index, index, index, index) -> !fir.slice<2>
+ %goodSlice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %badAddress = fir.array_coor %bad [%badSlice] %i
+ : (!fir.box<!fir.array<?x?xi32>>, !fir.slice<2>, index)
+ -> !fir.ref<i32>
+ %goodAddress = fir.array_coor %good [%goodSlice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%badAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%goodAddress) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @reduced_rank_rejected(
+ // CHECK-SAME: %[[BAD:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[GOOD:[^:]+]]: !fir.box
+ // CHECK: %[[BAD_SLICE:.*]] = fir.slice
+ // CHECK: %[[GOOD_SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: fir.box_dims %[[GOOD]],
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: fir.if
+ // CHECK: %[[GOOD_BYTE_BOX:.*]] = fir.convert %[[GOOD]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop %[[FAST_I:[^ ]+]] =
+ // CHECK: %[[BAD_FAST:.*]] = fir.array_coor %[[BAD]] [%[[BAD_SLICE]]] %[[FAST_I]]
+ // CHECK-NOT: fir.array_coor %[[GOOD]]
+ // CHECK: %[[GOOD_BYTE_ADDRESS:.*]] = fir.coordinate_of %[[GOOD_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[GOOD_FAST:.*]] = fir.convert %[[GOOD_BYTE_ADDRESS]]
+ // CHECK: fir.call @use_i32(%[[BAD_FAST]])
+ // CHECK: fir.call @use_i32(%[[GOOD_FAST]])
+ // CHECK: } else {
+ // CHECK: fir.do_loop %[[SLOW_I:[^ ]+]] =
+ // CHECK: %[[BAD_FALLBACK:.*]] = fir.array_coor %[[BAD]] [%[[BAD_SLICE]]] %[[SLOW_I]]
+ // CHECK: %[[GOOD_FALLBACK:.*]] = fir.array_coor %[[GOOD]] [%[[GOOD_SLICE]]] %[[SLOW_I]]
+ // CHECK: fir.call @use_i32(%[[BAD_FALLBACK]])
+ // CHECK: fir.call @use_i32(%[[GOOD_FALLBACK]])
+ // CHECK-NOT: fir.box_dims %[[BAD]],
+ // CHECK: return
+
+ // A non-defining slice block argument cannot be decoded and remains generic.
+ func.func @opaque_slice_rejected(
+ %a: !fir.box<!fir.array<?xi32>>, %slice: !fir.slice<1>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @opaque_slice_rejected(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[SLICE:[^:]+]]: !fir.slice<1>
+ // CHECK-NOT: fir.box_dims
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop
+ // CHECK: fir.array_coor %[[A]] {{.*}}[%[[SLICE]]]
+ // CHECK-NOT: fir.if
+ // CHECK: return
+
+ // A descriptor wrapper created inside the loop does not dominate the loop
+ // owner. Its slice must remain generic rather than moving descriptor
+ // metadata reads above the wrapper definition.
+ func.func @loop_local_descriptor_rejected(
+ %a: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %local = fir.rebox %a
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %address = fir.array_coor %local [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @loop_local_descriptor_rejected(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK-NOT: fir.box_dims
+ // CHECK-NOT: fir.if
+ // CHECK: fir.do_loop
+ // CHECK: %[[LOCAL:.*]] = fir.rebox %[[A]]
+ // CHECK: %[[ADDRESS:.*]] = fir.array_coor %[[LOCAL]]{{.*}}[%{{.*}}]
+ // CHECK: fir.call @use_i32(%[[ADDRESS]])
+ // CHECK-NOT: fir.if
+ // CHECK: return
+
+ // Leading-Scalar slices and non-shape lower-bound carriers are future
+ // extensions. These rejected forms do not affect an independent descriptor.
+ func.func @unsupported_shapes_rejected(
+ %allScalar: !fir.box<!fir.array<?x?xi32>>,
+ %leadingScalar: !fir.box<!fir.array<?x?xi32>>,
+ %shifted: !fir.box<!fir.array<?xi32>>,
+ %shapeShifted: !fir.box<!fir.array<?xi32>>,
+ %opaque: !fir.box<!fir.array<?xi32>>, %opaqueShape: !fir.shape<1>,
+ %good: !fir.box<!fir.array<?xi32>>, %origin: index) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %undef = fir.undefined index
+ %scalarSlice = fir.slice %c1, %undef, %undef,
+ %c1, %undef, %undef
+ : (index, index, index, index, index, index) -> !fir.slice<2>
+ %leadingScalarSlice = fir.slice %c1, %undef, %undef,
+ %c1, %c8, %c1
+ : (index, index, index, index, index, index) -> !fir.slice<2>
+ %unit = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ %shift = fir.shift %origin : (index) -> !fir.shift<1>
+ %shapeShift = fir.shape_shift %origin, %c8
+ : (index, index) -> !fir.shapeshift<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %x = fir.array_coor %allScalar [%scalarSlice] %c1, %c1
+ : (!fir.box<!fir.array<?x?xi32>>, !fir.slice<2>, index, index)
+ -> !fir.ref<i32>
+ %v = fir.array_coor %leadingScalar [%leadingScalarSlice] %c1, %i
+ : (!fir.box<!fir.array<?x?xi32>>, !fir.slice<2>, index, index)
+ -> !fir.ref<i32>
+ %y = fir.array_coor %shifted(%shift) [%unit] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.shift<1>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %w = fir.array_coor %shapeShifted(%shapeShift) [%unit] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.shapeshift<1>, !fir.slice<1>,
+ index) -> !fir.ref<i32>
+ %o = fir.array_coor %opaque(%opaqueShape) [%unit] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.shape<1>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %z = fir.array_coor %good [%unit] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%x) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%v) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%y) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%w) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%o) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%z) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @unsupported_shapes_rejected(
+ // CHECK-SAME: %[[SCALAR:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[LEADING_SCALAR:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[SHIFTED:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[SHAPE_SHIFTED:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[OPAQUE:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[OPAQUE_SHAPE:[^:]+]]: !fir.shape<1>
+ // CHECK-SAME: %[[GOOD:[^:]+]]: !fir.box
+ // CHECK: %[[SCALAR_SLICE:.*]] = fir.slice
+ // CHECK: %[[LEADING_SLICE:.*]] = fir.slice
+ // CHECK: %[[UNIT:.*]] = fir.slice
+ // CHECK: %[[SHIFT:.*]] = fir.shift
+ // CHECK: %[[SHAPE_SHIFT:.*]] = fir.shape_shift
+ // CHECK-NOT: fir.box_dims %[[SCALAR]],
+ // CHECK-NOT: fir.box_dims %[[LEADING_SCALAR]],
+ // CHECK-NOT: fir.box_dims %[[SHIFTED]],
+ // CHECK-NOT: fir.box_dims %[[SHAPE_SHIFTED]],
+ // CHECK-NOT: fir.box_dims %[[OPAQUE]],
+ // CHECK: fir.box_dims %[[GOOD]],
+ // CHECK-NOT: fir.box_dims %[[SCALAR]],
+ // CHECK-NOT: fir.box_dims %[[LEADING_SCALAR]],
+ // CHECK-NOT: fir.box_dims %[[SHIFTED]],
+ // CHECK-NOT: fir.box_dims %[[SHAPE_SHIFTED]],
+ // CHECK-NOT: fir.box_dims %[[OPAQUE]],
+ // CHECK: fir.if
+ // CHECK: %[[GOOD_BYTE_BOX:.*]] = fir.convert %[[GOOD]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.array_coor %[[SCALAR]] [%[[SCALAR_SLICE]]]
+ // CHECK: fir.array_coor %[[LEADING_SCALAR]] [%[[LEADING_SLICE]]]
+ // CHECK: fir.array_coor %[[SHIFTED]](%[[SHIFT]]) [%[[UNIT]]]
+ // CHECK: fir.array_coor %[[SHAPE_SHIFTED]](%[[SHAPE_SHIFT]]) [%[[UNIT]]]
+ // CHECK: fir.array_coor %[[OPAQUE]](%[[OPAQUE_SHAPE]]) [%[[UNIT]]]
+ // CHECK: fir.coordinate_of %[[GOOD_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: } else {
+ // CHECK: fir.array_coor %[[SCALAR]] [%[[SCALAR_SLICE]]]
+ // CHECK: fir.array_coor %[[LEADING_SCALAR]] [%[[LEADING_SLICE]]]
+ // CHECK: fir.array_coor %[[SHIFTED]](%[[SHIFT]]) [%[[UNIT]]]
+ // CHECK: fir.array_coor %[[SHAPE_SHIFTED]](%[[SHAPE_SHIFT]]) [%[[UNIT]]]
+ // CHECK: fir.array_coor %[[OPAQUE]](%[[OPAQUE_SHAPE]]) [%[[UNIT]]]
+ // CHECK: fir.array_coor %[[GOOD]] [%[[UNIT]]]
+ // CHECK-NOT: fir.box_dims %[[SCALAR]],
+ // CHECK-NOT: fir.box_dims %[[LEADING_SCALAR]],
+ // CHECK-NOT: fir.box_dims %[[SHIFTED]],
+ // CHECK-NOT: fir.box_dims %[[SHAPE_SHIFTED]],
+ // CHECK-NOT: fir.box_dims %[[OPAQUE]],
+ // CHECK: return
+
+ // CFI_MAX_RANK is the inclusive slice limit. The next rank must stay
+ // generic without indexing the fixed-size dimension metadata array.
+ func.func @rank15_accepted(
+ %a: !fir.box<!fir.array<?x?x?x?x?x?x?x?x?x?x?x?x?x?x?xi32>>,
+ %middle: index, %last: index) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %shape = fir.shape %c8, %c8, %c8, %c8, %c8, %c8, %c8, %c8,
+ %c8, %c8, %c8, %c8, %c8, %c8, %c8
+ : (index, index, index, index, index, index, index, index,
+ index, index, index, index, index, index, index) -> !fir.shape<15>
+ %slice = fir.slice %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1
+ : (index, index, index, index, index, index, index, index, index,
+ index, index, index, index, index, index, index, index, index,
+ index, index, index, index, index, index, index, index, index,
+ index, index, index, index, index, index, index, index, index,
+ index, index, index, index, index, index, index, index, index)
+ -> !fir.slice<15>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a(%shape) [%slice]
+ %i, %i, %i, %i, %i, %i, %i, %middle,
+ %i, %i, %i, %i, %i, %i, %last
+ : (!fir.box<!fir.array<?x?x?x?x?x?x?x?x?x?x?x?x?x?x?xi32>>,
+ !fir.shape<15>, !fir.slice<15>, index, index, index, index,
+ index, index, index, index, index, index, index, index, index,
+ index, index) -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @rank15_accepted(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[MIDDLE:[^:]+]]: index
+ // CHECK-SAME: %[[LAST:[^:]+]]: index
+ // CHECK: %[[SHAPE:.*]] = fir.shape
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK: %[[DIM0:.*]] = arith.constant 0 : index
+ // CHECK: %[[D0:.*]]:3 = fir.box_dims %[[A]], %[[DIM0]]
+ // CHECK: %[[ELEMENT_SIZE:.*]] = arith.constant 4 : index
+ // CHECK: arith.cmpi eq, %[[D0]]#2, %[[ELEMENT_SIZE]] : index
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK: fir.if
+ // CHECK: %[[FAST_ONE:.*]] = arith.constant 1 : index
+ // CHECK: %[[DIM1:.*]] = arith.constant 1 : index
+ // CHECK-NEXT: %[[D1:.*]]:3 = fir.box_dims %[[A]], %[[DIM1]]
+ // CHECK: %[[DIM2:.*]] = arith.constant 2 : index
+ // CHECK-NEXT: %[[D2:.*]]:3 = fir.box_dims %[[A]], %[[DIM2]]
+ // CHECK: %[[DIM3:.*]] = arith.constant 3 : index
+ // CHECK-NEXT: %[[D3:.*]]:3 = fir.box_dims %[[A]], %[[DIM3]]
+ // CHECK: %[[DIM4:.*]] = arith.constant 4 : index
+ // CHECK-NEXT: %[[D4:.*]]:3 = fir.box_dims %[[A]], %[[DIM4]]
+ // CHECK: %[[DIM5:.*]] = arith.constant 5 : index
+ // CHECK-NEXT: %[[D5:.*]]:3 = fir.box_dims %[[A]], %[[DIM5]]
+ // CHECK: %[[DIM6:.*]] = arith.constant 6 : index
+ // CHECK-NEXT: %[[D6:.*]]:3 = fir.box_dims %[[A]], %[[DIM6]]
+ // CHECK: %[[DIM7:.*]] = arith.constant 7 : index
+ // CHECK-NEXT: %[[D7:.*]]:3 = fir.box_dims %[[A]], %[[DIM7]]
+ // CHECK: %[[DIM8:.*]] = arith.constant 8 : index
+ // CHECK-NEXT: %[[D8:.*]]:3 = fir.box_dims %[[A]], %[[DIM8]]
+ // CHECK: %[[DIM9:.*]] = arith.constant 9 : index
+ // CHECK-NEXT: %[[D9:.*]]:3 = fir.box_dims %[[A]], %[[DIM9]]
+ // CHECK: %[[DIM10:.*]] = arith.constant 10 : index
+ // CHECK-NEXT: %[[D10:.*]]:3 = fir.box_dims %[[A]], %[[DIM10]]
+ // CHECK: %[[DIM11:.*]] = arith.constant 11 : index
+ // CHECK-NEXT: %[[D11:.*]]:3 = fir.box_dims %[[A]], %[[DIM11]]
+ // CHECK: %[[DIM12:.*]] = arith.constant 12 : index
+ // CHECK-NEXT: %[[D12:.*]]:3 = fir.box_dims %[[A]], %[[DIM12]]
+ // CHECK: %[[DIM13:.*]] = arith.constant 13 : index
+ // CHECK-NEXT: %[[D13:.*]]:3 = fir.box_dims %[[A]], %[[DIM13]]
+ // CHECK: %[[DIM14:.*]] = arith.constant 14 : index
+ // CHECK-NEXT: %[[D14:.*]]:3 = fir.box_dims %[[A]], %[[DIM14]]
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK: %[[BYTE_BOX:.*]] = fir.convert %[[A]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.do_loop %[[FAST_IV:[^ ]+]] =
+ // CHECK: %[[OFFSET0:.*]] = arith.subi %[[FAST_IV]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTES0:.*]] = arith.muli %[[OFFSET0]], %[[ELEMENT_SIZE]] : index
+ // CHECK: %[[OFFSET1:.*]] = arith.subi %[[FAST_IV]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTES1:.*]] = arith.muli %[[D1]]#2, %[[OFFSET1]] : index
+ // CHECK: %[[SUM1:.*]] = arith.addi %[[BYTES0]], %[[BYTES1]] : index
+ // CHECK: %[[OFFSET2:.*]] = arith.subi %[[FAST_IV]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTES2:.*]] = arith.muli %[[D2]]#2, %[[OFFSET2]] : index
+ // CHECK: %[[SUM2:.*]] = arith.addi %[[SUM1]], %[[BYTES2]] : index
+ // CHECK: %[[OFFSET3:.*]] = arith.subi %[[FAST_IV]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTES3:.*]] = arith.muli %[[D3]]#2, %[[OFFSET3]] : index
+ // CHECK: %[[SUM3:.*]] = arith.addi %[[SUM2]], %[[BYTES3]] : index
+ // CHECK: %[[OFFSET4:.*]] = arith.subi %[[FAST_IV]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTES4:.*]] = arith.muli %[[D4]]#2, %[[OFFSET4]] : index
+ // CHECK: %[[SUM4:.*]] = arith.addi %[[SUM3]], %[[BYTES4]] : index
+ // CHECK: %[[OFFSET5:.*]] = arith.subi %[[FAST_IV]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTES5:.*]] = arith.muli %[[D5]]#2, %[[OFFSET5]] : index
+ // CHECK: %[[SUM5:.*]] = arith.addi %[[SUM4]], %[[BYTES5]] : index
+ // CHECK: %[[OFFSET6:.*]] = arith.subi %[[FAST_IV]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTES6:.*]] = arith.muli %[[D6]]#2, %[[OFFSET6]] : index
+ // CHECK: %[[SUM6:.*]] = arith.addi %[[SUM5]], %[[BYTES6]] : index
+ // CHECK: %[[MIDDLE_OFFSET:.*]] = arith.subi %[[MIDDLE]], %[[FAST_ONE]] : index
+ // CHECK: %[[MIDDLE_BYTES:.*]] = arith.muli %[[D7]]#2, %[[MIDDLE_OFFSET]] : index
+ // CHECK: %[[SUM7:.*]] = arith.addi %[[SUM6]], %[[MIDDLE_BYTES]] : index
+ // CHECK: %[[OFFSET8:.*]] = arith.subi %[[FAST_IV]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTES8:.*]] = arith.muli %[[D8]]#2, %[[OFFSET8]] : index
+ // CHECK: %[[SUM8:.*]] = arith.addi %[[SUM7]], %[[BYTES8]] : index
+ // CHECK: %[[OFFSET9:.*]] = arith.subi %[[FAST_IV]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTES9:.*]] = arith.muli %[[D9]]#2, %[[OFFSET9]] : index
+ // CHECK: %[[SUM9:.*]] = arith.addi %[[SUM8]], %[[BYTES9]] : index
+ // CHECK: %[[OFFSET10:.*]] = arith.subi %[[FAST_IV]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTES10:.*]] = arith.muli %[[D10]]#2, %[[OFFSET10]] : index
+ // CHECK: %[[SUM10:.*]] = arith.addi %[[SUM9]], %[[BYTES10]] : index
+ // CHECK: %[[OFFSET11:.*]] = arith.subi %[[FAST_IV]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTES11:.*]] = arith.muli %[[D11]]#2, %[[OFFSET11]] : index
+ // CHECK: %[[SUM11:.*]] = arith.addi %[[SUM10]], %[[BYTES11]] : index
+ // CHECK: %[[OFFSET12:.*]] = arith.subi %[[FAST_IV]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTES12:.*]] = arith.muli %[[D12]]#2, %[[OFFSET12]] : index
+ // CHECK: %[[SUM12:.*]] = arith.addi %[[SUM11]], %[[BYTES12]] : index
+ // CHECK: %[[OFFSET13:.*]] = arith.subi %[[FAST_IV]], %[[FAST_ONE]] : index
+ // CHECK: %[[BYTES13:.*]] = arith.muli %[[D13]]#2, %[[OFFSET13]] : index
+ // CHECK: %[[SUM13:.*]] = arith.addi %[[SUM12]], %[[BYTES13]] : index
+ // CHECK: %[[LAST_OFFSET:.*]] = arith.subi %[[LAST]], %[[FAST_ONE]] : index
+ // CHECK: %[[LAST_BYTES:.*]] = arith.muli %[[D14]]#2, %[[LAST_OFFSET]] : index
+ // CHECK: %[[TOTAL:.*]] = arith.addi %[[SUM13]], %[[LAST_BYTES]] : index
+ // CHECK: %[[BYTE_ADDRESS:.*]] = fir.coordinate_of %[[BYTES]], %[[TOTAL]]
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[FAST_REF:.*]] = fir.convert %[[BYTE_ADDRESS]]
+ // CHECK-NEXT: fir.call @use_i32(%[[FAST_REF]])
+ // CHECK-NOT: fir.array_coor %[[A]]
+ // CHECK: } else {
+ // CHECK: fir.do_loop %[[SLOW_IV:[^ ]+]] =
+ // CHECK: %[[FALLBACK:.*]] = fir.array_coor %[[A]](%[[SHAPE]]) [%[[SLICE]]] %[[SLOW_IV]], %[[SLOW_IV]], %[[SLOW_IV]], %[[SLOW_IV]], %[[SLOW_IV]], %[[SLOW_IV]], %[[SLOW_IV]], %[[MIDDLE]], %[[SLOW_IV]], %[[SLOW_IV]], %[[SLOW_IV]], %[[SLOW_IV]], %[[SLOW_IV]], %[[SLOW_IV]], %[[LAST]]
+ // CHECK-NEXT: fir.call @use_i32(%[[FALLBACK]])
+ // CHECK-NOT: fir.array_coor %[[A]]
+ // CHECK: return
+
+ // Slice operands defined inside the selected owner must be read from the
+ // cloned loop. Using the original load in the fast branch would violate SSA
+ // dominance after the original loop is moved into the fallback region.
+ func.func @loop_local_slice_operand(
+ %a: !fir.box<!fir.array<?xi32>>, %lowerRef: !fir.ref<i64>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %lower = fir.load %lowerRef : !fir.ref<i64>
+ %slice = fir.slice %lower, %c8, %c1
+ : (i64, index, index) -> !fir.slice<1>
+ %address = fir.array_coor %a [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @loop_local_slice_operand(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[LOWER_REF:[^:]+]]: !fir.ref<i64>
+ // CHECK: fir.box_dims %[[A]],
+ // CHECK: fir.if
+ // CHECK: %[[FAST_ONE:.*]] = arith.constant 1 : index
+ // CHECK: fir.do_loop %[[FAST_I:[^ ]+]] =
+ // CHECK: %[[FAST_LOWER:.*]] = fir.load %[[LOWER_REF]]
+ // CHECK-NOT: fir.slice
+ // CHECK: %[[FAST_LOWER_INDEX:.*]] = fir.convert %[[FAST_LOWER]] : (i64) -> index
+ // CHECK: %[[FAST_ADJUST:.*]] = arith.subi %[[FAST_LOWER_INDEX]], %[[FAST_ONE]] : index
+ // CHECK: arith.addi {{.*}}, %[[FAST_ADJUST]] : index
+ // CHECK: %[[FAST_ADDRESS:.*]] = fir.coordinate_of
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[FAST_REF:.*]] = fir.convert %[[FAST_ADDRESS]]
+ // CHECK: fir.call @use_i32(%[[FAST_REF]])
+ // CHECK: } else {
+ // CHECK: fir.do_loop %[[SLOW_I:[^ ]+]] =
+ // CHECK: %[[SLOW_LOWER:.*]] = fir.load %[[LOWER_REF]]
+ // CHECK: %[[SLOW_SLICE:.*]] = fir.slice %[[SLOW_LOWER]],
+ // CHECK: %[[SLOW_ADDRESS:.*]] = fir.array_coor %[[A]] [%[[SLOW_SLICE]]] %[[SLOW_I]]
+ // CHECK: fir.call @use_i32(%[[SLOW_ADDRESS]])
+ // CHECK: return
+
+ // Rank 16 exceeds CFI_MAX_RANK and must remain generic without indexing the
+ // fixed-size dimension metadata array used by the versioned path.
+ func.func @rank16_rejected(
+ %a: !fir.box<!fir.array<?x?x?x?x?x?x?x?x?x?x?x?x?x?x?x?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %shape = fir.shape %c8, %c8, %c8, %c8, %c8, %c8, %c8, %c8,
+ %c8, %c8, %c8, %c8, %c8, %c8, %c8, %c8
+ : (index, index, index, index, index, index, index, index,
+ index, index, index, index, index, index, index, index)
+ -> !fir.shape<16>
+ %slice = fir.slice %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1, %c1, %c8, %c1,
+ %c1, %c8, %c1, %c1, %c8, %c1
+ : (index, index, index, index, index, index, index, index, index,
+ index, index, index, index, index, index, index, index, index,
+ index, index, index, index, index, index, index, index, index,
+ index, index, index, index, index, index, index, index, index,
+ index, index, index, index, index, index, index, index, index,
+ index, index, index) -> !fir.slice<16>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %a(%shape) [%slice]
+ %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i, %i
+ : (!fir.box<!fir.array<?x?x?x?x?x?x?x?x?x?x?x?x?x?x?x?xi32>>,
+ !fir.shape<16>, !fir.slice<16>, index, index, index, index,
+ index, index, index, index, index, index, index, index, index,
+ index, index, index) -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @rank16_rejected(
+ // CHECK-SAME: %[[A:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[A]],
+ // CHECK-NOT: fir.if
+ // CHECK: fir.array_coor %[[A]]{{.*}}[%[[SLICE]]]
+ // CHECK: return
+
+ // Optional and volatile descriptors keep the generic path because direct
+ // byte addressing must not bypass their established semantics. Verify the
+ // same rejection through fir.declare and fir.pack_array wrappers. Storage
+ // wrappers are outside this initial direct-sequence contract. A volatile
+ // result type is rejected independently of descriptor attributes. An
+ // independent direct sequence descriptor remains eligible.
+ func.func @descriptor_semantics_rejected(
+ %optionalRoot: !fir.box<!fir.array<?xi32>>,
+ %volatile: !fir.box<!fir.array<?xi32>> {fir.volatile},
+ %volatileResult: !fir.box<!fir.array<?xi32>>,
+ %heap: !fir.box<!fir.heap<!fir.array<?xi32>>>,
+ %pointer: !fir.box<!fir.ptr<!fir.array<?xi32>>>,
+ %declaredRoot: !fir.box<!fir.array<?xi32>>,
+ %packedRoot: !fir.box<!fir.array<?xi32>>,
+ %good: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %optional = fir.rebox %optionalRoot optional
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %declaredVolatile = fir.declare %declaredRoot
+ {fortran_attrs = #fir.var_attrs<volatile>,
+ uniq_name = "declaredVolatile"}
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %declaredOptional = fir.declare %packedRoot
+ {fortran_attrs = #fir.var_attrs<optional>,
+ uniq_name = "declaredOptional"}
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %packedOptional = fir.pack_array %declaredOptional heap whole
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %packedVolatile = fir.pack_array %declaredVolatile heap whole
+ : (!fir.box<!fir.array<?xi32>>) -> !fir.box<!fir.array<?xi32>>
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %x = fir.array_coor %optional [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %y = fir.array_coor %volatile [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %r = fir.array_coor %volatileResult [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32, volatile>
+ %h = fir.array_coor %heap [%slice] %i
+ : (!fir.box<!fir.heap<!fir.array<?xi32>>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %p = fir.array_coor %pointer [%slice] %i
+ : (!fir.box<!fir.ptr<!fir.array<?xi32>>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %d = fir.array_coor %declaredVolatile [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %q = fir.array_coor %packedOptional [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %v = fir.array_coor %packedVolatile [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %z = fir.array_coor %good [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%x) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%y) : (!fir.ref<i32>) -> ()
+ fir.call @use_volatile(%r) : (!fir.ref<i32, volatile>) -> ()
+ fir.call @use_i32(%h) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%p) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%d) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%q) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%v) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%z) : (!fir.ref<i32>) -> ()
+ }
+ fir.unpack_array %packedOptional to %declaredOptional heap
+ : !fir.box<!fir.array<?xi32>>
+ fir.unpack_array %packedVolatile to %declaredVolatile heap
+ : !fir.box<!fir.array<?xi32>>
+ return
+ }
+ // CHECK-LABEL: func.func @descriptor_semantics_rejected(
+ // CHECK-SAME: %[[OPTIONAL_ROOT:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[VOLATILE:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[VOLATILE_RESULT:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[HEAP:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[POINTER:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[DECLARED_ROOT:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[PACKED_ROOT:[^:]+]]: !fir.box
+ // CHECK-SAME: %[[GOOD:[^:]+]]: !fir.box
+ // CHECK: %[[OPTIONAL:.*]] = fir.rebox %[[OPTIONAL_ROOT]] optional
+ // CHECK: %[[DECLARED:.*]] = fir.declare %[[DECLARED_ROOT]]
+ // CHECK-SAME: fortran_attrs = #fir.var_attrs<volatile>
+ // CHECK: %[[DECLARED_OPTIONAL:.*]] = fir.declare %[[PACKED_ROOT]]
+ // CHECK-SAME: fortran_attrs = #fir.var_attrs<optional>
+ // CHECK: %[[PACKED:.*]] = fir.pack_array %[[DECLARED_OPTIONAL]] heap whole
+ // CHECK: %[[PACKED_VOLATILE:.*]] = fir.pack_array %[[DECLARED]] heap whole
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[OPTIONAL]],
+ // CHECK-NOT: fir.box_dims %[[VOLATILE]],
+ // CHECK-NOT: fir.box_dims %[[VOLATILE_RESULT]],
+ // CHECK-NOT: fir.box_dims %[[HEAP]],
+ // CHECK-NOT: fir.box_dims %[[POINTER]],
+ // CHECK-NOT: fir.box_dims %[[DECLARED]],
+ // CHECK-NOT: fir.box_dims %[[PACKED]],
+ // CHECK-NOT: fir.box_dims %[[PACKED_VOLATILE]],
+ // CHECK: fir.box_dims %[[GOOD]],
+ // CHECK-NOT: fir.box_dims %[[OPTIONAL]],
+ // CHECK-NOT: fir.box_dims %[[VOLATILE]],
+ // CHECK-NOT: fir.box_dims %[[VOLATILE_RESULT]],
+ // CHECK-NOT: fir.box_dims %[[HEAP]],
+ // CHECK-NOT: fir.box_dims %[[POINTER]],
+ // CHECK-NOT: fir.box_dims %[[DECLARED]],
+ // CHECK-NOT: fir.box_dims %[[PACKED]],
+ // CHECK-NOT: fir.box_dims %[[PACKED_VOLATILE]],
+ // CHECK: fir.if
+ // CHECK: %[[GOOD_BYTE_BOX:.*]] = fir.convert %[[GOOD]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.array_coor %[[OPTIONAL]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[VOLATILE]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[VOLATILE_RESULT]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[HEAP]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[POINTER]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[DECLARED]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[PACKED]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[PACKED_VOLATILE]]{{.*}}[%[[SLICE]]]
+ // CHECK: %[[GOOD_ADDRESS:.*]] = fir.coordinate_of %[[GOOD_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: %[[GOOD_FAST:.*]] = fir.convert %[[GOOD_ADDRESS]]
+ // CHECK-NOT: fir.array_coor %[[GOOD]]
+ // CHECK: fir.call @use_i32(%[[GOOD_FAST]])
+ // CHECK-NOT: fir.array_coor %[[GOOD]]
+ // CHECK: } else {
+ // CHECK: fir.array_coor %[[OPTIONAL]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[VOLATILE]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[VOLATILE_RESULT]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[HEAP]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[POINTER]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[DECLARED]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[PACKED]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[PACKED_VOLATILE]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.array_coor %[[GOOD]]{{.*}}[%[[SLICE]]]
+ // CHECK: fir.unpack_array %[[PACKED]] to %[[DECLARED_OPTIONAL]] heap
+ // CHECK: fir.unpack_array %[[PACKED_VOLATILE]] to %[[DECLARED]] heap
+ // CHECK: return
+
+ // A raw assumed-shape reference is not a descriptor and must remain on the
+ // generic path without disabling an independent supported descriptor.
+ func.func @non_box_descriptor_rejected(
+ %raw: !fir.ref<!fir.array<?xi32>>,
+ %good: !fir.box<!fir.array<?xi32>>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %rawAddress = fir.array_coor %raw [%slice] %i
+ : (!fir.ref<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %goodAddress = fir.array_coor %good [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%rawAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%goodAddress) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @non_box_descriptor_rejected(
+ // CHECK-SAME: %[[RAW:[^:]+]]: !fir.ref<!fir.array<?xi32>>
+ // CHECK-SAME: %[[GOOD:[^:]+]]: !fir.box
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK: fir.box_dims %[[GOOD]],
+ // CHECK: fir.if
+ // CHECK: %[[GOOD_BYTE_BOX:.*]] = fir.convert %[[GOOD]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: %[[RAW_FAST:.*]] = fir.array_coor %[[RAW]] [%[[SLICE]]]
+ // CHECK: fir.coordinate_of %[[GOOD_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: fir.call @use_i32(%[[RAW_FAST]])
+ // CHECK: } else {
+ // CHECK: fir.array_coor %[[RAW]] [%[[SLICE]]]
+ // CHECK: fir.array_coor %[[GOOD]] [%[[SLICE]]]
+ // CHECK: return
+
+ // A volatile descriptor type is rejected independently of a supported
+ // non-volatile polymorphic descriptor in the same owner.
+ func.func @class_descriptor_semantics(
+ %good: !fir.class<!fir.array<?xi32>>,
+ %volatile: !fir.class<!fir.array<?xi32>, volatile>) {
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %slice = fir.slice %c1, %c8, %c1
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %goodAddress = fir.array_coor %good [%slice] %i
+ : (!fir.class<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ %volatileAddress = fir.array_coor %volatile [%slice] %i
+ : (!fir.class<!fir.array<?xi32>, volatile>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%goodAddress) : (!fir.ref<i32>) -> ()
+ fir.call @use_i32(%volatileAddress) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @class_descriptor_semantics(
+ // CHECK-SAME: %[[GOOD:[^:]+]]: !fir.class<!fir.array<?xi32>>
+ // CHECK-SAME: %[[VOLATILE:[^:]+]]: !fir.class<!fir.array<?xi32>, volatile>
+ // CHECK: %[[SLICE:.*]] = fir.slice
+ // CHECK-NOT: fir.box_dims %[[VOLATILE]],
+ // CHECK: fir.box_dims %[[GOOD]],
+ // CHECK-NOT: fir.box_dims %[[VOLATILE]],
+ // CHECK: fir.if
+ // CHECK: %[[GOOD_BYTE_BOX:.*]] = fir.convert %[[GOOD]]
+ // CHECK-SAME: -> !fir.box<!fir.array<?xi8>>
+ // CHECK: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BYTE_BOX]]
+ // CHECK-SAME: -> !fir.ref<!fir.array<?xi8>>
+ // CHECK: fir.coordinate_of %[[GOOD_BYTES]],
+ // CHECK-SAME: -> !fir.ref<i8>
+ // CHECK: fir.array_coor %[[VOLATILE]] [%[[SLICE]]]
+ // CHECK: } else {
+ // CHECK: fir.array_coor %[[GOOD]] [%[[SLICE]]]
+ // CHECK: fir.array_coor %[[VOLATILE]] [%[[SLICE]]]
+ // CHECK: return
+
+}
diff --git a/flang/test/Transforms/loop-versioning.fir b/flang/test/Transforms/loop-versioning.fir
index fc67f2e087b25..1278a295e5efc 100644
--- a/flang/test/Transforms/loop-versioning.fir
+++ b/flang/test/Transforms/loop-versioning.fir
@@ -1,5 +1,8 @@
// RUN: fir-opt --loop-versioning %s | FileCheck %s
-
+// Repeat the existing checks with slice support disabled to ensure the option
+// preserves established slice-free transformation behavior.
+// RUN: fir-opt '--loop-versioning=enable-slices=false' %s | \
+// RUN: FileCheck %s
// subroutine sum1d(a, n)
// real*8 :: a(:)
diff --git a/utils/bazel/llvm-project-overlay/flang/lib/Optimizer/Transforms/BUILD.bazel b/utils/bazel/llvm-project-overlay/flang/lib/Optimizer/Transforms/BUILD.bazel
index 28dfb3e48f95b..9aaeebd09e555 100644
--- a/utils/bazel/llvm-project-overlay/flang/lib/Optimizer/Transforms/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/flang/lib/Optimizer/Transforms/BUILD.bazel
@@ -56,6 +56,7 @@ cc_library(
"//flang/lib/Optimizer/Support:FIRSupport",
"//flang/lib/Support:FortranSupport",
"//llvm:BinaryFormat",
+ "//llvm:Core",
"//llvm:Support",
"//mlir:AffineDialect",
"//mlir:AffineUtils",
>From c75c6d08eb5bb61f67d4b083ffe486d983ca4f0f Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Mon, 7 Sep 2026 10:18:06 +0400
Subject: [PATCH 2/5] [flang][test] Do not require overflow flags in slice
lowering checks
---
flang/test/Fir/loop-versioning-slices-target-layout.fir | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/flang/test/Fir/loop-versioning-slices-target-layout.fir b/flang/test/Fir/loop-versioning-slices-target-layout.fir
index e50e4cfa3842b..9f1038965ae0f 100644
--- a/flang/test/Fir/loop-versioning-slices-target-layout.fir
+++ b/flang/test/Fir/loop-versioning-slices-target-layout.fir
@@ -178,7 +178,7 @@ module attributes {
// CHECK: llvm.call @use_i32(%[[FAST_ADDR]])
// CHECK: ^[[FALLBACK]]:
// CHECK: %[[FALLBACK_ONE:.*]] = llvm.mlir.constant(1 : i64) : i64
-// CHECK: llvm.sub %[[LOWER]], %[[FALLBACK_ONE]] overflow<nsw, nuw> : i64
+// CHECK: llvm.sub %[[LOWER]], %[[FALLBACK_ONE]]
// CHECK: %[[FALLBACK_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
// CHECK: llvm.call @use_i32(%[[FALLBACK_ADDR]])
@@ -192,7 +192,7 @@ module attributes {
// CHECK: llvm.call @use_i32(%[[FAST_ADDR]])
// CHECK: ^[[FALLBACK]]:
// CHECK: %[[FALLBACK_ONE:.*]] = llvm.mlir.constant(1 : i64) : i64
-// CHECK: llvm.sub %[[NEGATIVE]], %[[FALLBACK_ONE]] overflow<nsw, nuw> : i64
+// CHECK: llvm.sub %[[NEGATIVE]], %[[FALLBACK_ONE]]
// CHECK: %[[FALLBACK_ADDR:.*]] = llvm.getelementptr {{.*}} : (!llvm.ptr, i64) -> !llvm.ptr, i8
// CHECK: llvm.call @use_i32(%[[FALLBACK_ADDR]])
>From 604957b32f8b7a3c1859b7ece031136dda90f4fc Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Thu, 10 Sep 2026 23:30:47 +0400
Subject: [PATCH 3/5] [flang] Fix ArgInfo initialization in LoopVersioning
---
flang/lib/Optimizer/Transforms/LoopVersioning.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/flang/lib/Optimizer/Transforms/LoopVersioning.cpp b/flang/lib/Optimizer/Transforms/LoopVersioning.cpp
index 2a6b8fd2aec3e..8b6e8317bc544 100644
--- a/flang/lib/Optimizer/Transforms/LoopVersioning.cpp
+++ b/flang/lib/Optimizer/Transforms/LoopVersioning.cpp
@@ -1398,7 +1398,7 @@ void LoopVersioningPass::runOnOperation() {
auto [rank, typeSize] =
getRankAndElementSize(kindMap, *dl, arg, /*isArgument=*/true);
if (rank != 0 && typeSize != 0)
- argsOfInterest.push_back({arg, typeSize, rank, {}});
+ argsOfInterest.push_back({arg, typeSize, rank, {}, {}});
}
if (argsOfInterest.empty()) {
>From 657cb5db8717df40fc8913a03fd5a240ae50c4b4 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Sat, 12 Sep 2026 14:30:58 +0400
Subject: [PATCH 4/5] [flang][test] Move slice versioning execution tests to
test-suite
Remove the compile-and-run tests from flang-rt and restore its temporary LLVMFlang feature wiring. Keep and strengthen structural source coverage for the default, wide-kind, and repacking configurations. Execution coverage is provided by the companion llvm-test-suite change.
---
.../loop-versioning-unit-slices-repack.f90 | 63 ---
.../Driver/loop-versioning-unit-slices.f90 | 368 ------------------
flang-rt/test/lit.cfg.py | 4 -
flang-rt/test/lit.site.cfg.py.in | 1 -
.../loop-versioning-slices-source.f90 | 365 ++++++++++++++++-
5 files changed, 354 insertions(+), 447 deletions(-)
delete mode 100644 flang-rt/test/Driver/loop-versioning-unit-slices-repack.f90
delete mode 100644 flang-rt/test/Driver/loop-versioning-unit-slices.f90
diff --git a/flang-rt/test/Driver/loop-versioning-unit-slices-repack.f90 b/flang-rt/test/Driver/loop-versioning-unit-slices-repack.f90
deleted file mode 100644
index dfff9a499d91c..0000000000000
--- a/flang-rt/test/Driver/loop-versioning-unit-slices-repack.f90
+++ /dev/null
@@ -1,63 +0,0 @@
-! Test FIR loop versioning by checking the generated fast path and verifying
-! that its execution produces the expected results.
-! Verify slice versioning through a frontend-generated fir.pack_array.
-! REQUIRES: llvm-flang
-! UNSUPPORTED: offload-cuda
-! RUN: %flang -S -O3 -fversion-loops-for-stride \
-! RUN: -frepack-arrays -frepack-arrays-contiguity=whole \
-! RUN: -mmlir --mlir-disable-threading \
-! RUN: -mmlir --mlir-print-ir-after=loop-versioning \
-! RUN: %s -o /dev/null 2>&1 | \
-! RUN: FileCheck %s --check-prefix=VERSIONED --enable-var-scope
-! RUN: %flang %isysroot -L"%libdir" -O3 -fversion-loops-for-stride \
-! RUN: -frepack-arrays -frepack-arrays-contiguity=whole %s -o %t
-! RUN: env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:%libdir" %t | FileCheck %s
-
-module loop_versioning_unit_slices_repack_m
- implicit none
-contains
- subroutine fill_repacked(values, indices, record)
- real, intent(inout) :: values(:, :)
- integer, intent(in) :: indices(2)
- character(*), intent(in) :: record
-
- read(record, *) values(2:3, indices)
- end subroutine
-end module
-
-program loop_versioning_unit_slices_repack
- use loop_versioning_unit_slices_repack_m, only: fill_repacked
- implicit none
- real :: storage(6, 2), expected(6, 2)
- integer :: indices(2)
-
- storage = -1.0
- expected = -1.0
- indices = [1, 2]
-
- ! The actual argument is noncontiguous in its first dimension. Whole-array
- ! repacking gives the callee a contiguous temporary, and the epilogue must
- ! copy the values written through the byte fast path back to storage.
- call fill_repacked(storage(1:5:2, :), indices, '1 2 3 4')
- expected(3:5:2, 1) = [1.0, 2.0]
- expected(3:5:2, 2) = [3.0, 4.0]
- if (any(storage /= expected)) error stop 1
-
- ! CHECK: PASS
- print '(A)', 'PASS'
-end program
-
-! The compile-time half proves that the executed repacking configuration uses
-! the packed descriptor in the guarded byte-address fast path.
-! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_repack_mPfill_repacked(
-! VERSIONED: %[[PACKED:.*]] = fir.pack_array %[[ORIGINAL:.*]] heap whole
-! VERSIONED: %[[DECLARED:.*]] = fir.declare %[[PACKED]]
-! VERSIONED: %[[PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
-! VERSIONED-NEXT: fir.if %[[PRED]]
-! VERSIONED: %[[BYTE_BOX:.*]] = fir.convert %[[DECLARED]]
-! VERSIONED-SAME: -> !fir.box<!fir.array<?xi8>>
-! VERSIONED: %[[BASE:.*]] = fir.box_addr %[[BYTE_BOX]]
-! VERSIONED: %[[ADDRESS:.*]] = fir.coordinate_of %[[BASE]],
-! VERSIONED-NEXT: %[[FAST:.*]] = fir.convert %[[ADDRESS]]
-! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[FAST]])
-! VERSIONED: fir.unpack_array %[[PACKED]] to %[[ORIGINAL]] heap
diff --git a/flang-rt/test/Driver/loop-versioning-unit-slices.f90 b/flang-rt/test/Driver/loop-versioning-unit-slices.f90
deleted file mode 100644
index 2151bb1cabdc2..0000000000000
--- a/flang-rt/test/Driver/loop-versioning-unit-slices.f90
+++ /dev/null
@@ -1,368 +0,0 @@
-! Test FIR loop versioning by checking the generated fast path and verifying
-! that its execution produces the expected results.
-! Verify runtime semantics for source-expressible static-unit slices.
-! The compiler source test separately proves byte fast paths for every
-! distinct positive access and ownership shape exercised below.
-! REQUIRES: llvm-flang
-! UNSUPPORTED: offload-cuda
-! RUN: %flang -S -O3 -fversion-loops-for-stride \
-! RUN: -mmlir --mlir-disable-threading \
-! RUN: -mmlir --mlir-print-ir-after=loop-versioning \
-! RUN: %s -o /dev/null 2>&1 | \
-! RUN: FileCheck %s --check-prefix=VERSIONED --enable-var-scope
-! RUN: %flang -S -O3 -fversion-loops-for-stride \
-! RUN: -fdefault-integer-8 -fdefault-real-8 \
-! RUN: -mmlir --mlir-disable-threading \
-! RUN: -mmlir --mlir-print-ir-after=loop-versioning \
-! RUN: %s -o /dev/null 2>&1 | \
-! RUN: FileCheck %s --check-prefix=VERSIONED --enable-var-scope
-! RUN: %flang %isysroot -L"%libdir" -O3 -fversion-loops-for-stride %s -o %t
-! RUN: env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:%libdir" %t | FileCheck %s
-! RUN: %flang %isysroot -L"%libdir" -O3 -fversion-loops-for-stride \
-! RUN: -fdefault-integer-8 -fdefault-real-8 %s -o %t.wide
-! RUN: env LD_LIBRARY_PATH="$LD_LIBRARY_PATH:%libdir" %t.wide | FileCheck %s
-
-module loop_versioning_unit_slices_m
- implicit none
-contains
- subroutine fill_slices(graph, gabor, indices, y, record)
- real, intent(inout) :: graph(:, :, :), gabor(:, :, :, :)
- integer, intent(in) :: indices(2)
- integer(kind=8), intent(in) :: y
- character(*), intent(in) :: record
-
- read(record, *) graph(:, :, indices), gabor(:, :, indices, y)
- end subroutine
-
- subroutine fill_offset_slices(graph, gabor, indices, y, record)
- real, intent(inout) :: graph(:, :, :), gabor(:, :, :, :)
- integer, intent(in) :: indices(2)
- integer(kind=8), intent(in) :: y
- character(*), intent(in) :: record
-
- read(record, *) graph(2:3, 2:3, indices), &
- gabor(2:3, 2:3, indices, y)
- end subroutine
-
- subroutine fill_rank2_patterns(values, lower0, lower1, indices, record)
- real, intent(inout) :: values(:, :)
- integer, intent(in) :: lower0, lower1, indices(2)
- character(*), intent(in) :: record
-
- read(record, *) values(lower0:lower0 + 1, indices), &
- values(lower1:lower1 + 1, indices)
- end subroutine
-
- subroutine fill_generalized(values, lower0, lower2, indices, record)
- real(kind=8), intent(inout) :: values(:, :, :)
- integer, intent(in) :: lower0, lower2, indices(2)
- character(*), intent(in) :: record
-
- read(record, *) values(lower0:lower0 + 1, indices, lower2:lower2 + 1)
- end subroutine
-
- subroutine fill_constant_scalar(values, indices, record)
- real, intent(inout) :: values(:, :, :)
- integer, intent(in) :: indices(2)
- character(*), intent(in) :: record
-
- read(record, *) values(2:3, indices, 1)
- end subroutine
-
- subroutine fill_isolated_descriptors(good, strided, indices, step, &
- good_record, strided_record)
- real, intent(inout) :: good(:, :), strided(:, :)
- integer, intent(in) :: indices(2), step
- character(*), intent(in) :: good_record, strided_record
-
- read(good_record, *) good(2:4, indices)
- read(strided_record, *) strided(1:5:step, indices)
- end subroutine
-
- subroutine fill_sequential_owners(values, indices, first_record, &
- second_record)
- real, intent(inout) :: values(:, :)
- integer, intent(in) :: indices(2)
- character(*), intent(in) :: first_record, second_record
-
- read(first_record, *) values(1:2, indices)
- read(second_record, *) values(4:5, indices)
- end subroutine
-end module
-
-program loop_versioning_unit_slices
- use loop_versioning_unit_slices_m, only: fill_constant_scalar, &
- fill_generalized, fill_isolated_descriptors, fill_offset_slices, &
- fill_rank2_patterns, fill_sequential_owners, fill_slices
- implicit none
- real :: graph(2, 2, 3), gabor(2, 2, 3, 2)
- real :: graph_storage(4, 4, 3), gabor_storage(4, 4, 3, 2)
- real :: graph_expected(4, 4, 3), gabor_expected(4, 4, 3, 2)
- real :: rank2_values(6, 2), rank2_expected(6, 2)
- real(kind=8) :: generalized_values(4, 3, 4)
- real(kind=8) :: generalized_expected(4, 3, 4)
- real :: good_values(6, 2), good_expected(6, 2)
- real :: strided_values(6, 2), strided_expected(6, 2)
- real :: sequential_values(6, 2), sequential_expected(6, 2)
- real :: constant_values(4, 2, 2), constant_expected(4, 2, 2)
- integer :: indices(2), section_indices(2)
- character(64) :: record
-
- ! The facerec-shaped rank-3 and rank-4 accesses establish the baseline fast
- ! addresses for trailing scalar dimensions and two independent descriptors.
- graph = -1.0
- gabor = -1.0
- indices = [1, 3]
- record = '1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16'
- call fill_slices(graph, gabor, indices, 2_8, record)
-
- if (any(graph(:, :, 1) /= reshape([1., 2., 3., 4.], [2, 2]))) &
- error stop 1
- if (any(graph(:, :, 2) /= -1.0)) error stop 2
- if (any(graph(:, :, 3) /= reshape([5., 6., 7., 8.], [2, 2]))) &
- error stop 3
- if (any(gabor(:, :, 1, 2) /= reshape([9., 10., 11., 12.], [2, 2]))) &
- error stop 4
- if (any(gabor(:, :, 2, 2) /= -1.0)) error stop 5
- if (any(gabor(:, :, 3, 2) /= &
- reshape([13., 14., 15., 16.], [2, 2]))) error stop 6
- if (any(gabor(:, :, :, 1) /= -1.0)) error stop 7
-
- ! The first dimension remains contiguous while the retained outer dimension
- ! has a nonstandard descriptor stride. This must use the byte fast path.
- graph_storage = -1.0
- gabor_storage = -1.0
- record = '17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32'
- call fill_slices(graph_storage(1:2, 1:4:2, :), &
- gabor_storage(1:2, 1:4:2, :, :), indices, 2_8, record)
- graph_expected = -1.0
- graph_expected(1:2, 1:4:2, 1) = reshape([17., 18., 19., 20.], [2, 2])
- graph_expected(1:2, 1:4:2, 3) = reshape([21., 22., 23., 24.], [2, 2])
- gabor_expected = -1.0
- gabor_expected(1:2, 1:4:2, 1, 2) = &
- reshape([25., 26., 27., 28.], [2, 2])
- gabor_expected(1:2, 1:4:2, 3, 2) = &
- reshape([29., 30., 31., 32.], [2, 2])
- if (any(graph_storage /= graph_expected)) error stop 8
- if (any(gabor_storage /= gabor_expected)) error stop 9
-
- ! A noncontiguous first dimension makes the runtime predicate false, so the
- ! original sliced access must preserve the same semantics in the fallback.
- graph_storage = -1.0
- gabor_storage = -1.0
- record = '33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48'
- call fill_slices(graph_storage(1:4:2, 1:2, :), &
- gabor_storage(1:4:2, 1:2, :, :), indices, 2_8, record)
- graph_expected = -1.0
- graph_expected(1:4:2, 1:2, 1) = reshape([33., 34., 35., 36.], [2, 2])
- graph_expected(1:4:2, 1:2, 3) = reshape([37., 38., 39., 40.], [2, 2])
- gabor_expected = -1.0
- gabor_expected(1:4:2, 1:2, 1, 2) = &
- reshape([41., 42., 43., 44.], [2, 2])
- gabor_expected(1:4:2, 1:2, 3, 2) = &
- reshape([45., 46., 47., 48.], [2, 2])
- if (any(graph_storage /= graph_expected)) error stop 10
- if (any(gabor_storage /= gabor_expected)) error stop 11
-
- ! Non-one section lower bounds exercise the retained-section correction in
- ! the byte fast path rather than relying only on structural FIR checks.
- graph_storage = -1.0
- gabor_storage = -1.0
- record = '49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64'
- call fill_offset_slices(graph_storage, gabor_storage, indices, 2_8, record)
- graph_expected = -1.0
- graph_expected(2:3, 2:3, 1) = reshape([49., 50., 51., 52.], [2, 2])
- graph_expected(2:3, 2:3, 3) = reshape([53., 54., 55., 56.], [2, 2])
- gabor_expected = -1.0
- gabor_expected(2:3, 2:3, 1, 2) = &
- reshape([57., 58., 59., 60.], [2, 2])
- gabor_expected(2:3, 2:3, 3, 2) = &
- reshape([61., 62., 63., 64.], [2, 2])
- if (any(graph_storage /= graph_expected)) error stop 12
- if (any(gabor_storage /= gabor_expected)) error stop 13
-
- ! Vector subscripts keep each section on direct fir.array_coor operations.
- ! Two supported owners of one descriptor use distinct dynamic lower bounds.
- rank2_values = -1.0
- rank2_expected = -1.0
- section_indices = [1, 2]
- call fill_rank2_patterns(rank2_values, 1, 4, section_indices, &
- '65 66 67 68 69 70 71 72')
- rank2_expected(1:2, 1:2) = reshape([65., 66., 67., 68.], [2, 2])
- rank2_expected(4:5, 1:2) = reshape([69., 70., 71., 72.], [2, 2])
- if (any(rank2_values /= rank2_expected)) error stop 14
-
- ! Section/Section/Section validates the generalized retained-dimension
- ! formula with eight-byte elements and non-one dynamic lower bounds. The
- ! facerec procedures above cover trailing scalar coordinates separately.
- generalized_values = -1.0_8
- generalized_expected = -1.0_8
- section_indices = [1, 3]
- call fill_generalized(generalized_values, 2, 2, section_indices, &
- '73 74 75 76 77 78 79 80')
- generalized_expected(2:3, 1, 2) = [73.0_8, 74.0_8]
- generalized_expected(2:3, 3, 2) = [75.0_8, 76.0_8]
- generalized_expected(2:3, 1, 3) = [77.0_8, 78.0_8]
- generalized_expected(2:3, 3, 3) = [79.0_8, 80.0_8]
- if (any(generalized_values /= generalized_expected)) error stop 15
-
- ! An unsupported dynamic-step descriptor must not disable an independent
- ! supported descriptor or be rewritten as if its step were one.
- good_values = -1.0
- good_expected = -1.0
- strided_values = -1.0
- strided_expected = -1.0
- section_indices = [1, 2]
- call fill_isolated_descriptors(good_values, strided_values, &
- section_indices, 2, &
- '81 82 83 84 85 86', &
- '87 88 89 90 91 92')
- good_expected(2:4, 1:2) = reshape([81., 82., 83., 84., 85., 86.], [3, 2])
- strided_expected(1:5:2, 1:2) = &
- reshape([87., 88., 89., 90., 91., 92.], [3, 2])
- if (any(good_values /= good_expected)) error stop 16
- if (any(strided_values /= strided_expected)) error stop 17
-
- ! One descriptor used by two sequential owners must retain the independently
- ! frozen slice facts of both loops.
- sequential_values = -1.0
- sequential_expected = -1.0
- call fill_sequential_owners(sequential_values, section_indices, &
- '93 94 95 96', '97 98 99 100')
- sequential_expected(1:2, 1:2) = reshape([93., 94., 95., 96.], [2, 2])
- sequential_expected(4:5, 1:2) = reshape([97., 98., 99., 100.], [2, 2])
- if (any(sequential_values /= sequential_expected)) error stop 18
-
- ! A constant-one Scalar coordinate has a zero outer byte contribution. The
- ! fast path must retain the correct element while folding the redundant
- ! stride multiplication before later canonicalization.
- constant_values = -1.0
- constant_expected = -1.0
- section_indices = [1, 2]
- call fill_constant_scalar(constant_values, section_indices, &
- '101 102 103 104')
- constant_expected(2:3, 1:2, 1) = &
- reshape([101.0, 102.0, 103.0, 104.0], [2, 2])
- if (any(constant_values /= constant_expected)) error stop 19
-
- ! A negative retained outer stride still satisfies the dimension-zero
- ! predicate and must therefore produce the same byte-fast-path addresses as
- ! the generic sliced access.
- graph_storage = -1.0
- gabor_storage = -1.0
- record = '105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120'
- call fill_slices(graph_storage(1:2, 4:2:-2, :), &
- gabor_storage(1:2, 4:2:-2, :, :), indices, 2_8, record)
- graph_expected = -1.0
- graph_expected(1:2, 4:2:-2, 1) = &
- reshape([105., 106., 107., 108.], [2, 2])
- graph_expected(1:2, 4:2:-2, 3) = &
- reshape([109., 110., 111., 112.], [2, 2])
- gabor_expected = -1.0
- gabor_expected(1:2, 4:2:-2, 1, 2) = &
- reshape([113., 114., 115., 116.], [2, 2])
- gabor_expected(1:2, 4:2:-2, 3, 2) = &
- reshape([117., 118., 119., 120.], [2, 2])
- if (any(graph_storage /= graph_expected)) error stop 20
- if (any(gabor_storage /= gabor_expected)) error stop 21
-
- ! CHECK: PASS
- print '(A)', 'PASS'
-end program
-
-! Compile-time checks are paired with the executions above. They prove that
-! every supported runtime helper contains the guarded byte-address fast path;
-! the executable checks independently validate the addresses it computes.
-
-! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_slices(
-! VERSIONED: %[[GRAPH_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
-! VERSIONED-NEXT: fir.if %[[GRAPH_PRED]]
-! VERSIONED: %[[GRAPH_BYTE_BOX:.*]] = fir.convert %[[GRAPH:[^ ]+]]
-! VERSIONED-SAME: {{.*}}-> !fir.box<!fir.array<?xi8>>
-! VERSIONED: %[[GRAPH_BASE:.*]] = fir.box_addr %[[GRAPH_BYTE_BOX]]
-! VERSIONED: %[[GRAPH_ADDRESS:.*]] = fir.coordinate_of %[[GRAPH_BASE]],
-! VERSIONED-NEXT: %[[GRAPH_FAST:.*]] = fir.convert %[[GRAPH_ADDRESS]]
-! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[GRAPH_FAST]])
-! VERSIONED: %[[GABOR_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
-! VERSIONED-NEXT: fir.if %[[GABOR_PRED]]
-! VERSIONED: %[[GABOR_BYTE_BOX:.*]] = fir.convert %[[GABOR:[^ ]+]]
-! VERSIONED-SAME: {{.*}}-> !fir.box<!fir.array<?xi8>>
-! VERSIONED: %[[GABOR_BASE:.*]] = fir.box_addr %[[GABOR_BYTE_BOX]]
-! VERSIONED: %[[GABOR_ADDRESS:.*]] = fir.coordinate_of %[[GABOR_BASE]],
-! VERSIONED-NEXT: %[[GABOR_FAST:.*]] = fir.convert %[[GABOR_ADDRESS]]
-! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[GABOR_FAST]])
-
-! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_offset_slices(
-! VERSIONED: %[[OFFSET_GRAPH_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
-! VERSIONED-NEXT: fir.if %[[OFFSET_GRAPH_PRED]]
-! VERSIONED: %[[OFFSET_GRAPH_BYTE_BOX:.*]] = fir.convert %[[OFFSET_GRAPH:[^ ]+]]
-! VERSIONED: %[[OFFSET_GRAPH_BASE:.*]] = fir.box_addr %[[OFFSET_GRAPH_BYTE_BOX]]
-! VERSIONED: %[[OFFSET_GRAPH_ADDRESS:.*]] = fir.coordinate_of %[[OFFSET_GRAPH_BASE]],
-! VERSIONED-NEXT: %[[OFFSET_GRAPH_FAST:.*]] = fir.convert %[[OFFSET_GRAPH_ADDRESS]]
-! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[OFFSET_GRAPH_FAST]])
-! VERSIONED: %[[OFFSET_GABOR_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
-! VERSIONED-NEXT: fir.if %[[OFFSET_GABOR_PRED]]
-! VERSIONED: %[[OFFSET_GABOR_BYTE_BOX:.*]] = fir.convert %[[OFFSET_GABOR:[^ ]+]]
-! VERSIONED: %[[OFFSET_GABOR_BASE:.*]] = fir.box_addr %[[OFFSET_GABOR_BYTE_BOX]]
-! VERSIONED: %[[OFFSET_GABOR_ADDRESS:.*]] = fir.coordinate_of %[[OFFSET_GABOR_BASE]],
-! VERSIONED-NEXT: %[[OFFSET_GABOR_FAST:.*]] = fir.convert %[[OFFSET_GABOR_ADDRESS]]
-! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[OFFSET_GABOR_FAST]])
-
-! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_rank2_patterns(
-! VERSIONED: %[[RANK2_FIRST_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
-! VERSIONED-NEXT: fir.if %[[RANK2_FIRST_PRED]]
-! VERSIONED: %[[RANK2_FIRST_BYTE_BOX:.*]] = fir.convert %[[RANK2:[^ ]+]]
-! VERSIONED: %[[RANK2_FIRST_BASE:.*]] = fir.box_addr %[[RANK2_FIRST_BYTE_BOX]]
-! VERSIONED: %[[RANK2_FIRST_ADDRESS:.*]] = fir.coordinate_of %[[RANK2_FIRST_BASE]],
-! VERSIONED-NEXT: %[[RANK2_FIRST_FAST:.*]] = fir.convert %[[RANK2_FIRST_ADDRESS]]
-! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[RANK2_FIRST_FAST]])
-! VERSIONED: %[[RANK2_SECOND_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
-! VERSIONED-NEXT: fir.if %[[RANK2_SECOND_PRED]]
-! VERSIONED: %[[RANK2_SECOND_BYTE_BOX:.*]] = fir.convert %[[RANK2]]
-! VERSIONED: %[[RANK2_SECOND_BASE:.*]] = fir.box_addr %[[RANK2_SECOND_BYTE_BOX]]
-! VERSIONED: %[[RANK2_SECOND_ADDRESS:.*]] = fir.coordinate_of %[[RANK2_SECOND_BASE]],
-! VERSIONED-NEXT: %[[RANK2_SECOND_FAST:.*]] = fir.convert %[[RANK2_SECOND_ADDRESS]]
-! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[RANK2_SECOND_FAST]])
-
-! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_generalized(
-! VERSIONED: %[[GENERAL_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
-! VERSIONED-NEXT: fir.if %[[GENERAL_PRED]]
-! VERSIONED: %[[GENERAL_BYTE_BOX:.*]] = fir.convert %[[GENERAL:[^ ]+]]
-! VERSIONED: %[[GENERAL_BASE:.*]] = fir.box_addr %[[GENERAL_BYTE_BOX]]
-! VERSIONED: %[[GENERAL_ADDRESS:.*]] = fir.coordinate_of %[[GENERAL_BASE]],
-! VERSIONED-NEXT: %[[GENERAL_FAST:.*]] = fir.convert %[[GENERAL_ADDRESS]]
-! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[GENERAL_FAST]])
-
-! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_constant_scalar(
-! VERSIONED: %[[CONSTANT_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
-! VERSIONED-NEXT: fir.if %[[CONSTANT_PRED]]
-! VERSIONED: %[[CONSTANT_BYTE_BOX:.*]] = fir.convert %[[CONSTANT:[^ ]+]]
-! VERSIONED: %[[CONSTANT_BASE:.*]] = fir.box_addr %[[CONSTANT_BYTE_BOX]]
-! VERSIONED: %[[CONSTANT_ADDRESS:.*]] = fir.coordinate_of %[[CONSTANT_BASE]],
-! VERSIONED-NEXT: %[[CONSTANT_FAST:.*]] = fir.convert %[[CONSTANT_ADDRESS]]
-! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[CONSTANT_FAST]])
-
-! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_isolated_descriptors(
-! VERSIONED: %[[GOOD_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
-! VERSIONED-NEXT: fir.if %[[GOOD_PRED]]
-! VERSIONED: %[[GOOD_BYTE_BOX:.*]] = fir.convert %[[GOOD:[^ ]+]]
-! VERSIONED: %[[GOOD_BASE:.*]] = fir.box_addr %[[GOOD_BYTE_BOX]]
-! VERSIONED: %[[GOOD_ADDRESS:.*]] = fir.coordinate_of %[[GOOD_BASE]],
-! VERSIONED-NEXT: %[[GOOD_FAST:.*]] = fir.convert %[[GOOD_ADDRESS]]
-! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[GOOD_FAST]])
-
-! VERSIONED-LABEL: func.func @_QMloop_versioning_unit_slices_mPfill_sequential_owners(
-! VERSIONED: %[[SEQUENTIAL_FIRST_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
-! VERSIONED-NEXT: fir.if %[[SEQUENTIAL_FIRST_PRED]]
-! VERSIONED: %[[SEQUENTIAL_FIRST_BYTE_BOX:.*]] = fir.convert %[[SEQUENTIAL:[^ ]+]]
-! VERSIONED: %[[SEQUENTIAL_FIRST_BASE:.*]] = fir.box_addr %[[SEQUENTIAL_FIRST_BYTE_BOX]]
-! VERSIONED: %[[SEQUENTIAL_FIRST_ADDRESS:.*]] = fir.coordinate_of %[[SEQUENTIAL_FIRST_BASE]],
-! VERSIONED-NEXT: %[[SEQUENTIAL_FIRST_FAST:.*]] = fir.convert %[[SEQUENTIAL_FIRST_ADDRESS]]
-! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[SEQUENTIAL_FIRST_FAST]])
-! VERSIONED: %[[SEQUENTIAL_SECOND_PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %{{.*}} : index
-! VERSIONED-NEXT: fir.if %[[SEQUENTIAL_SECOND_PRED]]
-! VERSIONED: %[[SEQUENTIAL_SECOND_BYTE_BOX:.*]] = fir.convert %[[SEQUENTIAL]]
-! VERSIONED: %[[SEQUENTIAL_SECOND_BASE:.*]] = fir.box_addr %[[SEQUENTIAL_SECOND_BYTE_BOX]]
-! VERSIONED: %[[SEQUENTIAL_SECOND_ADDRESS:.*]] = fir.coordinate_of %[[SEQUENTIAL_SECOND_BASE]],
-! VERSIONED-NEXT: %[[SEQUENTIAL_SECOND_FAST:.*]] = fir.convert %[[SEQUENTIAL_SECOND_ADDRESS]]
-! VERSIONED-NEXT: %{{.*}} = fir.call @_FortranAioInputReal{{32|64}}({{.*}}, %[[SEQUENTIAL_SECOND_FAST]])
diff --git a/flang-rt/test/lit.cfg.py b/flang-rt/test/lit.cfg.py
index 091bcbdd9b941..74958d8155ef6 100644
--- a/flang-rt/test/lit.cfg.py
+++ b/flang-rt/test/lit.cfg.py
@@ -101,10 +101,6 @@ def shjoin(args, sep=" "):
# Library path of libflang_rt.runtime.a/.so (for lib search path when using non-Flang driver for linking and LD_LIBRARY_PATH)
config.substitutions.append(("%libdir", config.flang_rt_output_resource_lib_dir))
-# Some driver tests require LLVM Flang-specific options.
-if getattr(config, "fortran_compiler_id", "") == "LLVMFlang":
- config.available_features.add("llvm-flang")
-
# For CUDA offloading, additional steps (device linking) and libraries (cudart) are needed.
if config.flang_rt_experimental_offload_support == "CUDA":
config.available_features.add("offload-cuda")
diff --git a/flang-rt/test/lit.site.cfg.py.in b/flang-rt/test/lit.site.cfg.py.in
index a2b402327f607..8e3c902d4a3a6 100644
--- a/flang-rt/test/lit.site.cfg.py.in
+++ b/flang-rt/test/lit.site.cfg.py.in
@@ -13,7 +13,6 @@ config.flang_rt_experimental_offload_support = "@FLANG_RT_EXPERIMENTAL_OFFLOAD_S
config.flang_rt_fortran_modules = @FLANG_RT_FORTRAN_MODULES@
config.cc = "@CMAKE_C_COMPILER@"
config.flang = "@CMAKE_Fortran_COMPILER@"
-config.fortran_compiler_id = "@CMAKE_Fortran_COMPILER_ID@"
config.osx_sysroot = path(r"@CMAKE_OSX_SYSROOT@")
config.target_triple = "@LLVM_TARGET_TRIPLE@"
diff --git a/flang/test/Transforms/loop-versioning-slices-source.f90 b/flang/test/Transforms/loop-versioning-slices-source.f90
index fc57c9efb3521..69edf5011a327 100644
--- a/flang/test/Transforms/loop-versioning-slices-source.f90
+++ b/flang/test/Transforms/loop-versioning-slices-source.f90
@@ -18,6 +18,13 @@
! RUN: -mmlir --mlir-print-ir-after=loop-versioning \
! RUN: %s -o /dev/null 2>&1 | \
! RUN: FileCheck %s --check-prefix=REPACK --enable-var-scope
+! RUN: %flang -S -O3 -fversion-loops-for-stride \
+! RUN: -fdefault-integer-8 -fdefault-real-8 \
+! RUN: -frepack-arrays -frepack-arrays-contiguity=whole \
+! RUN: -mmlir --mlir-disable-threading \
+! RUN: -mmlir --mlir-print-ir-after=loop-versioning \
+! RUN: %s -o /dev/null 2>&1 | \
+! RUN: FileCheck %s --check-prefix=WIDE-REPACK --enable-var-scope
! Verify that source-expressible slice forms reach the byte-address fast path
! through both the fc1 and driver pipelines. The additional driver modes make
@@ -35,9 +42,10 @@ subroutine facerec_slices(graph, gabor, indices, y)
read(*, *) graph(:, :, indices), gabor(:, :, indices, y)
end subroutine
-! Keep the frontend wrapper configuration in a compiler test. The runtime test
-! separately verifies that values written through the repacked fast path are
-! copied back to a noncontiguous actual argument.
+! Keep the frontend wrapper configuration in a compiler test. The execution
+! test in llvm-test-suite/Fortran/UnitTests/loop-versioning-slices verifies that
+! values written through the repacked fast path are copied back to a
+! noncontiguous actual argument.
subroutine repacked_slice(values, indices)
implicit none
real, intent(inout) :: values(:, :)
@@ -138,15 +146,175 @@ subroutine sequential_owners(values, indices)
! DRIVER: IR Dump After LoopVersioning
! DRIVER-LABEL: func.func @_QPfacerec_slices(
-! DRIVER: fir.if
-! DRIVER: !fir.ref<!fir.array<?xi8>>
-! DRIVER: fir.coordinate_of
+! DRIVER: fir.do_loop
+! DRIVER: %[[GRAPH_D0:.*]]:3 = fir.box_dims %[[GRAPH_DESC:[^,]+]],
+! DRIVER: %[[GRAPH_SIZE:.*]] = arith.constant 4 : index
+! DRIVER-NEXT: %[[GRAPH_PRED:.*]] = arith.cmpi eq, %[[GRAPH_D0]]#2, %[[GRAPH_SIZE]] : index
+! DRIVER: fir.if %[[GRAPH_PRED]]
+! DRIVER: %[[GRAPH_BOX:.*]] = fir.convert %[[GRAPH_DESC]]
+! DRIVER: %[[GRAPH_BYTES:.*]] = fir.box_addr %[[GRAPH_BOX]]
+! DRIVER-SAME: -> !fir.ref<!fir.array<?xi8>>
+! DRIVER: %[[GRAPH_ADDRESS:.*]] = fir.coordinate_of %[[GRAPH_BYTES]],
+! DRIVER-NEXT: %[[GRAPH_FAST:.*]] = fir.convert %[[GRAPH_ADDRESS]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[GRAPH_FAST]])
! DRIVER: } else {
-! DRIVER: fir.array_coor
+! DRIVER: %[[GRAPH_FALLBACK:.*]] = fir.array_coor %[[GRAPH_DESC]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[GRAPH_FALLBACK]])
+! DRIVER: fir.do_loop
+! DRIVER: %[[GABOR_D0:.*]]:3 = fir.box_dims %[[GABOR_DESC:[^,]+]],
+! DRIVER: %[[GABOR_SIZE:.*]] = arith.constant 4 : index
+! DRIVER-NEXT: %[[GABOR_PRED:.*]] = arith.cmpi eq, %[[GABOR_D0]]#2, %[[GABOR_SIZE]] : index
+! DRIVER: fir.if %[[GABOR_PRED]]
+! DRIVER: %[[GABOR_BOX:.*]] = fir.convert %[[GABOR_DESC]]
+! DRIVER: %[[GABOR_BYTES:.*]] = fir.box_addr %[[GABOR_BOX]]
+! DRIVER: %[[GABOR_ADDRESS:.*]] = fir.coordinate_of %[[GABOR_BYTES]],
+! DRIVER-NEXT: %[[GABOR_FAST:.*]] = fir.convert %[[GABOR_ADDRESS]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[GABOR_FAST]])
+! DRIVER: } else {
+! DRIVER: %[[GABOR_FALLBACK:.*]] = fir.array_coor %[[GABOR_DESC]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[GABOR_FALLBACK]])
! DRIVER-LABEL: func.func @_QPrepacked_slice(
+! DRIVER: fir.do_loop
+! DRIVER: %[[REPACKED_D0:.*]]:3 = fir.box_dims %[[REPACKED_DESC:[^,]+]],
+! DRIVER: %[[SIZE:.*]] = arith.constant 4 : index
+! DRIVER-NEXT: %[[PRED:.*]] = arith.cmpi eq, %[[REPACKED_D0]]#2, %[[SIZE]] : index
+! DRIVER: fir.if %[[PRED]]
+! DRIVER: %[[REPACKED_BOX:.*]] = fir.convert %[[REPACKED_DESC]]
+! DRIVER: %[[REPACKED_BYTES:.*]] = fir.box_addr %[[REPACKED_BOX]]
+! DRIVER: %[[REPACKED_ADDRESS:.*]] = fir.coordinate_of %[[REPACKED_BYTES]],
+! DRIVER-NEXT: %[[REPACKED_FAST:.*]] = fir.convert %[[REPACKED_ADDRESS]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[REPACKED_FAST]])
+! DRIVER: } else {
+! DRIVER: %[[REPACKED_FALLBACK:.*]] = fir.array_coor %[[REPACKED_DESC]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[REPACKED_FALLBACK]])
+! DRIVER-LABEL: func.func @_QPoffset_slices(
+! DRIVER: fir.do_loop
+! DRIVER: %[[OFFSET_GRAPH_D0:.*]]:3 = fir.box_dims %[[OFFSET_GRAPH_DESC:[^,]+]],
+! DRIVER: %[[SIZE:.*]] = arith.constant 4 : index
+! DRIVER-NEXT: %[[PRED:.*]] = arith.cmpi eq, %[[OFFSET_GRAPH_D0]]#2, %[[SIZE]] : index
+! DRIVER: fir.if %[[PRED]]
+! DRIVER: %[[OFFSET_GRAPH_BOX:.*]] = fir.convert %[[OFFSET_GRAPH_DESC]]
+! DRIVER: %[[OFFSET_GRAPH_BYTES:.*]] = fir.box_addr %[[OFFSET_GRAPH_BOX]]
+! DRIVER: %[[OFFSET_GRAPH_ADDRESS:.*]] = fir.coordinate_of %[[OFFSET_GRAPH_BYTES]],
+! DRIVER-NEXT: %[[OFFSET_GRAPH_FAST:.*]] = fir.convert %[[OFFSET_GRAPH_ADDRESS]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[OFFSET_GRAPH_FAST]])
+! DRIVER: } else {
+! DRIVER: %[[OFFSET_GRAPH_FALLBACK:.*]] = fir.array_coor %[[OFFSET_GRAPH_DESC]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[OFFSET_GRAPH_FALLBACK]])
+! DRIVER: fir.do_loop
+! DRIVER: %[[OFFSET_GABOR_D0:.*]]:3 = fir.box_dims %[[OFFSET_GABOR_DESC:[^,]+]],
+! DRIVER: %[[OFFSET_GABOR_SIZE:.*]] = arith.constant 4 : index
+! DRIVER-NEXT: %[[OFFSET_GABOR_PRED:.*]] = arith.cmpi eq, %[[OFFSET_GABOR_D0]]#2, %[[OFFSET_GABOR_SIZE]] : index
+! DRIVER: fir.if %[[OFFSET_GABOR_PRED]]
+! DRIVER: %[[OFFSET_GABOR_BOX:.*]] = fir.convert %[[OFFSET_GABOR_DESC]]
+! DRIVER: %[[OFFSET_GABOR_BYTES:.*]] = fir.box_addr %[[OFFSET_GABOR_BOX]]
+! DRIVER: %[[OFFSET_GABOR_ADDRESS:.*]] = fir.coordinate_of %[[OFFSET_GABOR_BYTES]],
+! DRIVER-NEXT: %[[OFFSET_GABOR_FAST:.*]] = fir.convert %[[OFFSET_GABOR_ADDRESS]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[OFFSET_GABOR_FAST]])
+! DRIVER: } else {
+! DRIVER: %[[OFFSET_GABOR_FALLBACK:.*]] = fir.array_coor %[[OFFSET_GABOR_DESC]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[OFFSET_GABOR_FALLBACK]])
+! DRIVER-LABEL: func.func @_QPrank2_patterns(
+! DRIVER: fir.do_loop
+! DRIVER: %[[RANK2_D0:.*]]:3 = fir.box_dims %[[RANK2_DESC:[^,]+]],
+! DRIVER: %[[RANK2_SIZE0:.*]] = arith.constant 4 : index
+! DRIVER-NEXT: %[[RANK2_PRED0:.*]] = arith.cmpi eq, %[[RANK2_D0]]#2, %[[RANK2_SIZE0]] : index
+! DRIVER: fir.if %[[RANK2_PRED0]]
+! DRIVER: %[[RANK2_BOX0:.*]] = fir.convert %[[RANK2_DESC]]
+! DRIVER: %[[RANK2_BYTES0:.*]] = fir.box_addr %[[RANK2_BOX0]]
+! DRIVER: %[[RANK2_ADDRESS0:.*]] = fir.coordinate_of %[[RANK2_BYTES0]],
+! DRIVER-NEXT: %[[RANK2_FAST0:.*]] = fir.convert %[[RANK2_ADDRESS0]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[RANK2_FAST0]])
+! DRIVER: } else {
+! DRIVER: %[[RANK2_FALLBACK0:.*]] = fir.array_coor %[[RANK2_DESC]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[RANK2_FALLBACK0]])
+! DRIVER: fir.do_loop
+! DRIVER: %[[RANK2_D1:.*]]:3 = fir.box_dims %[[RANK2_DESC]],
+! DRIVER: %[[RANK2_SIZE1:.*]] = arith.constant 4 : index
+! DRIVER-NEXT: %[[RANK2_PRED1:.*]] = arith.cmpi eq, %[[RANK2_D1]]#2, %[[RANK2_SIZE1]] : index
+! DRIVER: fir.if %[[RANK2_PRED1]]
+! DRIVER: %[[RANK2_BOX1:.*]] = fir.convert %[[RANK2_DESC]]
+! DRIVER: %[[RANK2_BYTES1:.*]] = fir.box_addr %[[RANK2_BOX1]]
+! DRIVER: %[[RANK2_ADDRESS1:.*]] = fir.coordinate_of %[[RANK2_BYTES1]],
+! DRIVER-NEXT: %[[RANK2_FAST1:.*]] = fir.convert %[[RANK2_ADDRESS1]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[RANK2_FAST1]])
+! DRIVER: } else {
+! DRIVER: %[[RANK2_FALLBACK1:.*]] = fir.array_coor %[[RANK2_DESC]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[RANK2_FALLBACK1]])
+! DRIVER-LABEL: func.func @_QPgeneralized_slice(
+! DRIVER: fir.do_loop
+! DRIVER: %[[GENERAL_D0:.*]]:3 = fir.box_dims %[[GENERAL_DESC:[^,]+]],
+! DRIVER: %[[SIZE:.*]] = arith.constant 8 : index
+! DRIVER-NEXT: %[[PRED:.*]] = arith.cmpi eq, %[[GENERAL_D0]]#2, %[[SIZE]] : index
+! DRIVER: fir.if %[[PRED]]
+! DRIVER: %[[GENERAL_BOX:.*]] = fir.convert %[[GENERAL_DESC]]
+! DRIVER: %[[GENERAL_BYTES:.*]] = fir.box_addr %[[GENERAL_BOX]]
+! DRIVER: %[[GENERAL_ADDRESS:.*]] = fir.coordinate_of %[[GENERAL_BYTES]],
+! DRIVER-NEXT: %[[GENERAL_FAST:.*]] = fir.convert %[[GENERAL_ADDRESS]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[GENERAL_FAST]])
+! DRIVER: } else {
+! DRIVER: %[[GENERAL_FALLBACK:.*]] = fir.array_coor %[[GENERAL_DESC]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[GENERAL_FALLBACK]])
+! DRIVER-LABEL: func.func @_QPconstant_scalar(
+! DRIVER: fir.do_loop
+! DRIVER: %[[CONSTANT_D0:.*]]:3 = fir.box_dims %[[CONSTANT_DESC:[^,]+]],
+! DRIVER: %[[SIZE:.*]] = arith.constant 4 : index
+! DRIVER-NEXT: %[[PRED:.*]] = arith.cmpi eq, %[[CONSTANT_D0]]#2, %[[SIZE]] : index
+! DRIVER: fir.if %[[PRED]]
+! DRIVER: %[[CONSTANT_BOX:.*]] = fir.convert %[[CONSTANT_DESC]]
+! DRIVER: %[[CONSTANT_BYTES:.*]] = fir.box_addr %[[CONSTANT_BOX]]
+! DRIVER: %[[CONSTANT_ADDRESS:.*]] = fir.coordinate_of %[[CONSTANT_BYTES]],
+! DRIVER-NEXT: %[[CONSTANT_FAST:.*]] = fir.convert %[[CONSTANT_ADDRESS]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[CONSTANT_FAST]])
+! DRIVER: } else {
+! DRIVER: %[[CONSTANT_FALLBACK:.*]] = fir.array_coor %[[CONSTANT_DESC]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[CONSTANT_FALLBACK]])
+! DRIVER-LABEL: func.func @_QPisolated_descriptors(
+! DRIVER: fir.do_loop
+! DRIVER: %[[GOOD_D0:.*]]:3 = fir.box_dims %[[GOOD_DESC:[^,]+]],
+! DRIVER: %[[SIZE:.*]] = arith.constant 4 : index
+! DRIVER-NEXT: %[[PRED:.*]] = arith.cmpi eq, %[[GOOD_D0]]#2, %[[SIZE]] : index
+! DRIVER: fir.if %[[PRED]]
+! DRIVER: %[[GOOD_BOX:.*]] = fir.convert %[[GOOD_DESC]]
+! DRIVER: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BOX]]
+! DRIVER: %[[GOOD_ADDRESS:.*]] = fir.coordinate_of %[[GOOD_BYTES]],
+! DRIVER-NEXT: %[[GOOD_FAST:.*]] = fir.convert %[[GOOD_ADDRESS]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[GOOD_FAST]])
+! DRIVER: } else {
+! DRIVER: %[[GOOD_FALLBACK:.*]] = fir.array_coor %[[GOOD_DESC]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[GOOD_FALLBACK]])
+! DRIVER-LABEL: func.func @_QPsequential_owners(
+! DRIVER: fir.do_loop
+! DRIVER: %[[SEQUENTIAL_D0:.*]]:3 = fir.box_dims %[[SEQUENTIAL_DESC:[^,]+]],
+! DRIVER: %[[SIZE:.*]] = arith.constant 4 : index
+! DRIVER-NEXT: %[[PRED:.*]] = arith.cmpi eq, %[[SEQUENTIAL_D0]]#2, %[[SIZE]] : index
+! DRIVER: fir.if %[[PRED]]
+! DRIVER: %[[SEQUENTIAL_BOX0:.*]] = fir.convert %[[SEQUENTIAL_DESC]]
+! DRIVER: %[[SEQUENTIAL_BYTES0:.*]] = fir.box_addr %[[SEQUENTIAL_BOX0]]
+! DRIVER: %[[SEQUENTIAL_ADDRESS0:.*]] = fir.coordinate_of %[[SEQUENTIAL_BYTES0]],
+! DRIVER-NEXT: %[[SEQUENTIAL_FAST0:.*]] = fir.convert %[[SEQUENTIAL_ADDRESS0]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[SEQUENTIAL_FAST0]])
+! DRIVER: } else {
+! DRIVER: %[[SEQUENTIAL_FALLBACK0:.*]] = fir.array_coor %[[SEQUENTIAL_DESC]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[SEQUENTIAL_FALLBACK0]])
+! DRIVER: fir.do_loop
+! DRIVER: %[[SEQUENTIAL_D1:.*]]:3 = fir.box_dims %[[SEQUENTIAL_DESC]],
+! DRIVER: %[[SEQUENTIAL_SIZE1:.*]] = arith.constant 4 : index
+! DRIVER-NEXT: %[[SEQUENTIAL_PRED1:.*]] = arith.cmpi eq, %[[SEQUENTIAL_D1]]#2, %[[SEQUENTIAL_SIZE1]] : index
+! DRIVER: fir.if %[[SEQUENTIAL_PRED1]]
+! DRIVER: %[[SEQUENTIAL_BOX1:.*]] = fir.convert %[[SEQUENTIAL_DESC]]
+! DRIVER: %[[SEQUENTIAL_BYTES1:.*]] = fir.box_addr %[[SEQUENTIAL_BOX1]]
+! DRIVER: %[[SEQUENTIAL_ADDRESS1:.*]] = fir.coordinate_of %[[SEQUENTIAL_BYTES1]],
+! DRIVER-NEXT: %[[SEQUENTIAL_FAST1:.*]] = fir.convert %[[SEQUENTIAL_ADDRESS1]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[SEQUENTIAL_FAST1]])
+! DRIVER: } else {
+! DRIVER: %[[SEQUENTIAL_FALLBACK1:.*]] = fir.array_coor %[[SEQUENTIAL_DESC]]
+! DRIVER-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[SEQUENTIAL_FALLBACK1]])
! WIDE-LABEL: func.func @_QPfacerec_slices(
! WIDE-SAME: !fir.box<!fir.array<?x?x?xf64>>
+! WIDE: %[[WIDE_GABOR:.*]] = fir.declare {{.*}}uniq_name = "_QFfacerec_slicesEgabor"
+! WIDE: %[[WIDE_GABOR_REBOX:.*]] = fir.rebox %[[WIDE_GABOR]]
! WIDE: %[[WIDE_GRAPH:.*]] = fir.declare {{.*}}uniq_name = "_QFfacerec_slicesEgraph"
! WIDE: %[[WIDE_GRAPH_REBOX:.*]] = fir.rebox %[[WIDE_GRAPH]]
! WIDE: fir.box_dims %[[WIDE_GRAPH_REBOX]],
@@ -154,18 +322,172 @@ subroutine sequential_owners(values, indices)
! WIDE: %[[WIDE_SIZE:.*]] = arith.constant 8 : index
! WIDE: %[[WIDE_PRED:.*]] = arith.cmpi eq, %[[WIDE_D0]]#2, %[[WIDE_SIZE]] : index
! WIDE: fir.if %[[WIDE_PRED]]
-! WIDE: %[[WIDE_ADDRESS:.*]] = fir.coordinate_of {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! WIDE: %[[WIDE_GRAPH_BOX:.*]] = fir.convert %[[WIDE_GRAPH]]
+! WIDE: %[[WIDE_GRAPH_BYTES:.*]] = fir.box_addr %[[WIDE_GRAPH_BOX]]
+! WIDE: %[[WIDE_ADDRESS:.*]] = fir.coordinate_of %[[WIDE_GRAPH_BYTES]], {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
! WIDE-NEXT: %[[WIDE_FAST:.*]] = fir.convert %[[WIDE_ADDRESS]] : (!fir.ref<i8>) -> !fir.ref<f64>
! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[WIDE_FAST]])
! WIDE: } else {
-! WIDE: %[[WIDE_FALLBACK:.*]] = fir.array_coor
+! WIDE: %[[WIDE_FALLBACK:.*]] = fir.array_coor %[[WIDE_GRAPH]]
! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[WIDE_FALLBACK]])
+! WIDE: fir.do_loop
+! WIDE: %[[WIDE_GABOR_D0:.*]]:3 = fir.box_dims %[[WIDE_GABOR]],
+! WIDE: %[[WIDE_GABOR_SIZE:.*]] = arith.constant 8 : index
+! WIDE-NEXT: %[[WIDE_GABOR_PRED:.*]] = arith.cmpi eq, %[[WIDE_GABOR_D0]]#2, %[[WIDE_GABOR_SIZE]] : index
+! WIDE: fir.if %[[WIDE_GABOR_PRED]]
+! WIDE: %[[WIDE_GABOR_BOX:.*]] = fir.convert %[[WIDE_GABOR]]
+! WIDE: %[[WIDE_GABOR_BYTES:.*]] = fir.box_addr %[[WIDE_GABOR_BOX]]
+! WIDE: %[[WIDE_GABOR_ADDRESS:.*]] = fir.coordinate_of %[[WIDE_GABOR_BYTES]],
+! WIDE-NEXT: %[[WIDE_GABOR_FAST:.*]] = fir.convert %[[WIDE_GABOR_ADDRESS]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[WIDE_GABOR_FAST]])
+! WIDE: } else {
+! WIDE: %[[WIDE_GABOR_FALLBACK:.*]] = fir.array_coor %[[WIDE_GABOR]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[WIDE_GABOR_FALLBACK]])
! WIDE-LABEL: func.func @_QPrepacked_slice(
+! WIDE: fir.do_loop
+! WIDE: %[[REPACKED_D0:.*]]:3 = fir.box_dims %[[REPACKED_DESC:[^,]+]],
+! WIDE: %[[SIZE:.*]] = arith.constant 8 : index
+! WIDE-NEXT: %[[PRED:.*]] = arith.cmpi eq, %[[REPACKED_D0]]#2, %[[SIZE]] : index
+! WIDE: fir.if %[[PRED]]
+! WIDE: %[[REPACKED_BOX:.*]] = fir.convert %[[REPACKED_DESC]]
+! WIDE: %[[REPACKED_BYTES:.*]] = fir.box_addr %[[REPACKED_BOX]]
+! WIDE: %[[REPACKED_ADDRESS:.*]] = fir.coordinate_of %[[REPACKED_BYTES]],
+! WIDE-NEXT: %[[REPACKED_FAST:.*]] = fir.convert %[[REPACKED_ADDRESS]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[REPACKED_FAST]])
+! WIDE: } else {
+! WIDE: %[[REPACKED_FALLBACK:.*]] = fir.array_coor %[[REPACKED_DESC]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[REPACKED_FALLBACK]])
+! WIDE-LABEL: func.func @_QPoffset_slices(
+! WIDE: fir.do_loop
+! WIDE: %[[OFFSET_GRAPH_D0:.*]]:3 = fir.box_dims %[[OFFSET_GRAPH_DESC:[^,]+]],
+! WIDE: %[[SIZE:.*]] = arith.constant 8 : index
+! WIDE-NEXT: %[[PRED:.*]] = arith.cmpi eq, %[[OFFSET_GRAPH_D0]]#2, %[[SIZE]] : index
+! WIDE: fir.if %[[PRED]]
+! WIDE: %[[OFFSET_GRAPH_BOX:.*]] = fir.convert %[[OFFSET_GRAPH_DESC]]
+! WIDE: %[[OFFSET_GRAPH_BYTES:.*]] = fir.box_addr %[[OFFSET_GRAPH_BOX]]
+! WIDE: %[[OFFSET_GRAPH_ADDRESS:.*]] = fir.coordinate_of %[[OFFSET_GRAPH_BYTES]],
+! WIDE-NEXT: %[[OFFSET_GRAPH_FAST:.*]] = fir.convert %[[OFFSET_GRAPH_ADDRESS]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[OFFSET_GRAPH_FAST]])
+! WIDE: } else {
+! WIDE: %[[OFFSET_GRAPH_FALLBACK:.*]] = fir.array_coor %[[OFFSET_GRAPH_DESC]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[OFFSET_GRAPH_FALLBACK]])
+! WIDE: fir.do_loop
+! WIDE: %[[OFFSET_GABOR_D0:.*]]:3 = fir.box_dims %[[OFFSET_GABOR_DESC:[^,]+]],
+! WIDE: %[[OFFSET_GABOR_SIZE:.*]] = arith.constant 8 : index
+! WIDE-NEXT: %[[OFFSET_GABOR_PRED:.*]] = arith.cmpi eq, %[[OFFSET_GABOR_D0]]#2, %[[OFFSET_GABOR_SIZE]] : index
+! WIDE: fir.if %[[OFFSET_GABOR_PRED]]
+! WIDE: %[[OFFSET_GABOR_BOX:.*]] = fir.convert %[[OFFSET_GABOR_DESC]]
+! WIDE: %[[OFFSET_GABOR_BYTES:.*]] = fir.box_addr %[[OFFSET_GABOR_BOX]]
+! WIDE: %[[OFFSET_GABOR_ADDRESS:.*]] = fir.coordinate_of %[[OFFSET_GABOR_BYTES]],
+! WIDE-NEXT: %[[OFFSET_GABOR_FAST:.*]] = fir.convert %[[OFFSET_GABOR_ADDRESS]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[OFFSET_GABOR_FAST]])
+! WIDE: } else {
+! WIDE: %[[OFFSET_GABOR_FALLBACK:.*]] = fir.array_coor %[[OFFSET_GABOR_DESC]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[OFFSET_GABOR_FALLBACK]])
+! WIDE-LABEL: func.func @_QPrank2_patterns(
+! WIDE: fir.do_loop
+! WIDE: %[[RANK2_D0:.*]]:3 = fir.box_dims %[[RANK2_DESC:[^,]+]],
+! WIDE: %[[RANK2_SIZE0:.*]] = arith.constant 8 : index
+! WIDE-NEXT: %[[RANK2_PRED0:.*]] = arith.cmpi eq, %[[RANK2_D0]]#2, %[[RANK2_SIZE0]] : index
+! WIDE: fir.if %[[RANK2_PRED0]]
+! WIDE: %[[RANK2_BOX0:.*]] = fir.convert %[[RANK2_DESC]]
+! WIDE: %[[RANK2_BYTES0:.*]] = fir.box_addr %[[RANK2_BOX0]]
+! WIDE: %[[RANK2_ADDRESS0:.*]] = fir.coordinate_of %[[RANK2_BYTES0]],
+! WIDE-NEXT: %[[RANK2_FAST0:.*]] = fir.convert %[[RANK2_ADDRESS0]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[RANK2_FAST0]])
+! WIDE: } else {
+! WIDE: %[[RANK2_FALLBACK0:.*]] = fir.array_coor %[[RANK2_DESC]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[RANK2_FALLBACK0]])
+! WIDE: fir.do_loop
+! WIDE: %[[RANK2_D1:.*]]:3 = fir.box_dims %[[RANK2_DESC]],
+! WIDE: %[[RANK2_SIZE1:.*]] = arith.constant 8 : index
+! WIDE-NEXT: %[[RANK2_PRED1:.*]] = arith.cmpi eq, %[[RANK2_D1]]#2, %[[RANK2_SIZE1]] : index
+! WIDE: fir.if %[[RANK2_PRED1]]
+! WIDE: %[[RANK2_BOX1:.*]] = fir.convert %[[RANK2_DESC]]
+! WIDE: %[[RANK2_BYTES1:.*]] = fir.box_addr %[[RANK2_BOX1]]
+! WIDE: %[[RANK2_ADDRESS1:.*]] = fir.coordinate_of %[[RANK2_BYTES1]],
+! WIDE-NEXT: %[[RANK2_FAST1:.*]] = fir.convert %[[RANK2_ADDRESS1]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[RANK2_FAST1]])
+! WIDE: } else {
+! WIDE: %[[RANK2_FALLBACK1:.*]] = fir.array_coor %[[RANK2_DESC]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[RANK2_FALLBACK1]])
+! WIDE-LABEL: func.func @_QPgeneralized_slice(
+! WIDE: fir.do_loop
+! WIDE: %[[GENERAL_D0:.*]]:3 = fir.box_dims %[[GENERAL_DESC:[^,]+]],
+! WIDE: %[[SIZE:.*]] = arith.constant 8 : index
+! WIDE-NEXT: %[[PRED:.*]] = arith.cmpi eq, %[[GENERAL_D0]]#2, %[[SIZE]] : index
+! WIDE: fir.if %[[PRED]]
+! WIDE: %[[GENERAL_BOX:.*]] = fir.convert %[[GENERAL_DESC]]
+! WIDE: %[[GENERAL_BYTES:.*]] = fir.box_addr %[[GENERAL_BOX]]
+! WIDE: %[[GENERAL_ADDRESS:.*]] = fir.coordinate_of %[[GENERAL_BYTES]],
+! WIDE-NEXT: %[[GENERAL_FAST:.*]] = fir.convert %[[GENERAL_ADDRESS]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[GENERAL_FAST]])
+! WIDE: } else {
+! WIDE: %[[GENERAL_FALLBACK:.*]] = fir.array_coor %[[GENERAL_DESC]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[GENERAL_FALLBACK]])
+! WIDE-LABEL: func.func @_QPconstant_scalar(
+! WIDE: fir.do_loop
+! WIDE: %[[CONSTANT_D0:.*]]:3 = fir.box_dims %[[CONSTANT_DESC:[^,]+]],
+! WIDE: %[[SIZE:.*]] = arith.constant 8 : index
+! WIDE-NEXT: %[[PRED:.*]] = arith.cmpi eq, %[[CONSTANT_D0]]#2, %[[SIZE]] : index
+! WIDE: fir.if %[[PRED]]
+! WIDE: %[[CONSTANT_BOX:.*]] = fir.convert %[[CONSTANT_DESC]]
+! WIDE: %[[CONSTANT_BYTES:.*]] = fir.box_addr %[[CONSTANT_BOX]]
+! WIDE: %[[CONSTANT_ADDRESS:.*]] = fir.coordinate_of %[[CONSTANT_BYTES]],
+! WIDE-NEXT: %[[CONSTANT_FAST:.*]] = fir.convert %[[CONSTANT_ADDRESS]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[CONSTANT_FAST]])
+! WIDE: } else {
+! WIDE: %[[CONSTANT_FALLBACK:.*]] = fir.array_coor %[[CONSTANT_DESC]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[CONSTANT_FALLBACK]])
+! WIDE-LABEL: func.func @_QPisolated_descriptors(
+! WIDE: fir.do_loop
+! WIDE: %[[GOOD_D0:.*]]:3 = fir.box_dims %[[GOOD_DESC:[^,]+]],
+! WIDE: %[[SIZE:.*]] = arith.constant 8 : index
+! WIDE-NEXT: %[[PRED:.*]] = arith.cmpi eq, %[[GOOD_D0]]#2, %[[SIZE]] : index
+! WIDE: fir.if %[[PRED]]
+! WIDE: %[[GOOD_BOX:.*]] = fir.convert %[[GOOD_DESC]]
+! WIDE: %[[GOOD_BYTES:.*]] = fir.box_addr %[[GOOD_BOX]]
+! WIDE: %[[GOOD_ADDRESS:.*]] = fir.coordinate_of %[[GOOD_BYTES]],
+! WIDE-NEXT: %[[GOOD_FAST:.*]] = fir.convert %[[GOOD_ADDRESS]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[GOOD_FAST]])
+! WIDE: } else {
+! WIDE: %[[GOOD_FALLBACK:.*]] = fir.array_coor %[[GOOD_DESC]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[GOOD_FALLBACK]])
+! WIDE-LABEL: func.func @_QPsequential_owners(
+! WIDE: fir.do_loop
+! WIDE: %[[SEQUENTIAL_D0:.*]]:3 = fir.box_dims %[[SEQUENTIAL_DESC:[^,]+]],
+! WIDE: %[[SIZE:.*]] = arith.constant 8 : index
+! WIDE-NEXT: %[[PRED:.*]] = arith.cmpi eq, %[[SEQUENTIAL_D0]]#2, %[[SIZE]] : index
+! WIDE: fir.if %[[PRED]]
+! WIDE: %[[SEQUENTIAL_BOX0:.*]] = fir.convert %[[SEQUENTIAL_DESC]]
+! WIDE: %[[SEQUENTIAL_BYTES0:.*]] = fir.box_addr %[[SEQUENTIAL_BOX0]]
+! WIDE: %[[SEQUENTIAL_ADDRESS0:.*]] = fir.coordinate_of %[[SEQUENTIAL_BYTES0]],
+! WIDE-NEXT: %[[SEQUENTIAL_FAST0:.*]] = fir.convert %[[SEQUENTIAL_ADDRESS0]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[SEQUENTIAL_FAST0]])
+! WIDE: } else {
+! WIDE: %[[SEQUENTIAL_FALLBACK0:.*]] = fir.array_coor %[[SEQUENTIAL_DESC]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[SEQUENTIAL_FALLBACK0]])
+! WIDE: fir.do_loop
+! WIDE: %[[SEQUENTIAL_D1:.*]]:3 = fir.box_dims %[[SEQUENTIAL_DESC]],
+! WIDE: %[[SEQUENTIAL_SIZE1:.*]] = arith.constant 8 : index
+! WIDE-NEXT: %[[SEQUENTIAL_PRED1:.*]] = arith.cmpi eq, %[[SEQUENTIAL_D1]]#2, %[[SEQUENTIAL_SIZE1]] : index
+! WIDE: fir.if %[[SEQUENTIAL_PRED1]]
+! WIDE: %[[SEQUENTIAL_BOX1:.*]] = fir.convert %[[SEQUENTIAL_DESC]]
+! WIDE: %[[SEQUENTIAL_BYTES1:.*]] = fir.box_addr %[[SEQUENTIAL_BOX1]]
+! WIDE: %[[SEQUENTIAL_ADDRESS1:.*]] = fir.coordinate_of %[[SEQUENTIAL_BYTES1]],
+! WIDE-NEXT: %[[SEQUENTIAL_FAST1:.*]] = fir.convert %[[SEQUENTIAL_ADDRESS1]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[SEQUENTIAL_FAST1]])
+! WIDE: } else {
+! WIDE: %[[SEQUENTIAL_FALLBACK1:.*]] = fir.array_coor %[[SEQUENTIAL_DESC]]
+! WIDE-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[SEQUENTIAL_FALLBACK1]])
! REPACK-LABEL: func.func @_QPrepacked_slice(
-! REPACK: %[[PACKED:.*]] = fir.pack_array
+! REPACK: %[[PACKED:.*]] = fir.pack_array %[[ORIGINAL:.*]] heap whole
! REPACK: %[[DECLARED:.*]] = fir.declare %[[PACKED]]
-! REPACK: fir.if
+! REPACK: fir.do_loop
+! REPACK: %[[SIZE:.*]] = arith.constant 4 : index
+! REPACK-NEXT: %[[PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %[[SIZE]] : index
+! REPACK: fir.if %[[PRED]]
! REPACK: %[[BYTE_BOX:.*]] = fir.convert %[[DECLARED]]
! REPACK-SAME: -> !fir.box<!fir.array<?xi8>>
! REPACK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
@@ -175,8 +497,29 @@ subroutine sequential_owners(values, indices)
! REPACK: } else {
! REPACK: %[[FALLBACK:.*]] = fir.array_coor %[[DECLARED]]
! REPACK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal32({{.*}}, %[[FALLBACK]])
+! REPACK: fir.unpack_array %[[PACKED]] to %[[ORIGINAL]] heap
! REPACK-LABEL: func.func @_QPoffset_slices(
+! WIDE-REPACK-LABEL: func.func @_QPrepacked_slice(
+! WIDE-REPACK-SAME: !fir.box<!fir.array<?x?xf64>>
+! WIDE-REPACK: %[[PACKED:.*]] = fir.pack_array %[[ORIGINAL:.*]] heap whole
+! WIDE-REPACK: %[[DECLARED:.*]] = fir.declare %[[PACKED]]
+! WIDE-REPACK: fir.do_loop
+! WIDE-REPACK: %[[SIZE:.*]] = arith.constant 8 : index
+! WIDE-REPACK-NEXT: %[[PRED:.*]] = arith.cmpi eq, %{{.*}}#2, %[[SIZE]] : index
+! WIDE-REPACK: fir.if %[[PRED]]
+! WIDE-REPACK: %[[BYTE_BOX:.*]] = fir.convert %[[DECLARED]]
+! WIDE-REPACK-SAME: -> !fir.box<!fir.array<?xi8>>
+! WIDE-REPACK: %[[BYTES:.*]] = fir.box_addr %[[BYTE_BOX]]
+! WIDE-REPACK: %[[BYTE_ADDRESS:.*]] = fir.coordinate_of %[[BYTES]], {{.*}} : (!fir.ref<!fir.array<?xi8>>, index) -> !fir.ref<i8>
+! WIDE-REPACK-NEXT: %[[FAST:.*]] = fir.convert %[[BYTE_ADDRESS]] : (!fir.ref<i8>) -> !fir.ref<f64>
+! WIDE-REPACK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[FAST]])
+! WIDE-REPACK: } else {
+! WIDE-REPACK: %[[FALLBACK:.*]] = fir.array_coor %[[DECLARED]]
+! WIDE-REPACK-NEXT: %{{.*}} = fir.call @_FortranAioInputReal64({{.*}}, %[[FALLBACK]])
+! WIDE-REPACK: fir.unpack_array %[[PACKED]] to %[[ORIGINAL]] heap
+! WIDE-REPACK-LABEL: func.func @_QPoffset_slices(
+
! CHECK-LABEL: func.func @_QPoffset_slices(
! CHECK: %[[OFFSET_GRAPH_SLICE:.*]] = fir.slice
! CHECK: fir.if
>From e2263381e6036683a8e37905edaac2b2971207a5 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Sat, 12 Sep 2026 15:42:35 +0400
Subject: [PATCH 5/5] [flang][test] Account for integer convert
canonicalization
---
.../loop-versioning-unit-slices.fir | 52 ++++++++++++++++---
1 file changed, 44 insertions(+), 8 deletions(-)
diff --git a/flang/test/Transforms/loop-versioning-unit-slices.fir b/flang/test/Transforms/loop-versioning-unit-slices.fir
index 3bba38378abc1..31743b75c68bd 100644
--- a/flang/test/Transforms/loop-versioning-unit-slices.fir
+++ b/flang/test/Transforms/loop-versioning-unit-slices.fir
@@ -737,8 +737,8 @@ module attributes {
// CHECK-NOT: fir.box_dims %[[BAD]],
// CHECK: return
- // Canonicalization may fold the converted i1-rooted step, but must not make
- // that descriptor eligible. The independent ordinary unit slice still is.
+ // Canonicalization folds the explicit i1-to-i8 zero extension to positive
+ // one, making both it and the ordinary unit slice eligible.
// CANONICALIZED-LABEL: func.func @i1_operands_rejected(
// CANONICALIZED-SAME: %[[BAD_STEP:[^:]+]]: !fir.box
// CANONICALIZED-SAME: %[[BAD_INDEX:[^:]+]]: !fir.box
@@ -748,18 +748,17 @@ module attributes {
// CANONICALIZED-NOT: fir.box_dims %[[BAD_STEP]],
// CANONICALIZED-NOT: fir.box_dims %[[BAD_INDEX]],
// CANONICALIZED-NOT: fir.box_dims %[[BAD_LOWER]],
- // CANONICALIZED-NOT: fir.box_dims %[[CONVERTED_STEP]],
+ // CANONICALIZED: fir.box_dims %[[CONVERTED_STEP]],
// CANONICALIZED: fir.box_dims %[[GOOD]],
// CANONICALIZED-NOT: fir.box_dims %[[BAD_STEP]],
// CANONICALIZED-NOT: fir.box_dims %[[BAD_INDEX]],
// CANONICALIZED-NOT: fir.box_dims %[[BAD_LOWER]],
- // CANONICALIZED-NOT: fir.box_dims %[[CONVERTED_STEP]],
// CANONICALIZED: return
- // Reject raw i1 values in every consumed operand position. Also reject a
- // conversion chain rooted at i1: canonicalization may combine it into a
- // direct i1-to-index conversion and fold the set bit to -1. An independent
- // descriptor with an ordinary unit step must remain eligible.
+ // Reject raw i1 values in every consumed operand position. Without
+ // canonicalization, also conservatively reject the explicit i1-to-i8-to-index
+ // chain. An independent descriptor with an ordinary unit step must remain
+ // eligible.
func.func @i1_operands_rejected(
%badStepArg: !fir.box<!fir.array<?xi32>>,
%badIndexArg: !fir.box<!fir.array<?xi32>>,
@@ -864,6 +863,43 @@ module attributes {
// CHECK-NOT: fir.box_dims %[[CONVERTED_STEP]],
// CHECK: return
+ // A direct i1-to-index conversion sign-extends the set bit to -1, so it must
+ // remain ineligible even when canonicalization materializes that constant.
+ func.func @direct_i1_to_index_step_rejected(
+ %arg: !fir.box<!fir.array<?xi32>>) {
+ %true = arith.constant true
+ %c1 = arith.constant 1 : index
+ %c8 = arith.constant 8 : index
+ %step = fir.convert %true : (i1) -> index
+ %slice = fir.slice %c1, %c8, %step
+ : (index, index, index) -> !fir.slice<1>
+ fir.do_loop %i = %c1 to %c8 step %c1 {
+ %address = fir.array_coor %arg [%slice] %i
+ : (!fir.box<!fir.array<?xi32>>, !fir.slice<1>, index)
+ -> !fir.ref<i32>
+ fir.call @use_i32(%address) : (!fir.ref<i32>) -> ()
+ }
+ return
+ }
+ // CHECK-LABEL: func.func @direct_i1_to_index_step_rejected(
+ // CHECK-SAME: %[[ARG:[^:]+]]: !fir.box
+ // CHECK: %[[STEP:.*]] = fir.convert %{{.*}} : (i1) -> index
+ // CHECK: %[[SLICE:.*]] = fir.slice %{{.*}}, %{{.*}}, %[[STEP]]
+ // CHECK-NOT: fir.box_dims %[[ARG]],
+ // CHECK: fir.do_loop
+ // CHECK: fir.array_coor %[[ARG]] [%[[SLICE]]]
+ // CHECK-NOT: fir.box_dims %[[ARG]],
+ // CHECK: return
+ // CANONICALIZED-LABEL: func.func @direct_i1_to_index_step_rejected(
+ // CANONICALIZED-SAME: %[[ARG:[^:]+]]: !fir.box
+ // CANONICALIZED: %[[STEP:.*]] = arith.constant -1 : index
+ // CANONICALIZED: %[[SLICE:.*]] = fir.slice %{{.*}}, %{{.*}}, %[[STEP]]
+ // CANONICALIZED-NOT: fir.box_dims %[[ARG]],
+ // CANONICALIZED: fir.do_loop
+ // CANONICALIZED: fir.array_coor %[[ARG]] [%[[SLICE]]]
+ // CANONICALIZED-NOT: fir.box_dims %[[ARG]],
+ // CANONICALIZED: return
+
// A signed one-bit intermediate changes a positive one to -1 when it is
// widened. Preserve the section-lower adjustment instead of looking through
// such a conversion chain and eliding it.
More information about the flang-commits
mailing list