Skip to content

Commit 75e4812

Browse files
committed
add lubounds test
1 parent a89c512 commit 75e4812

5 files changed

Lines changed: 233 additions & 1 deletion

File tree

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ endif()
4343

4444
foreach(a IN ITEMS
4545
iso_fortran_binding
46-
allocate contiguous
46+
allocate bounds contiguous
4747
array bool error exception
4848
iterator
4949
glibc malloc

src/bounds/bounds.c

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <ISO_Fortran_binding.h>
4+
#include <inttypes.h>
5+
6+
#include "bounds.h"
7+
8+
#ifdef __cplusplus
9+
extern "C" {
10+
#endif
11+
12+
/* ==================== 1D Version ==================== */
13+
bool c_bounder(CFI_cdesc_t *a,
14+
CFI_index_t fortran_lower, // Original Fortran lower bound
15+
CFI_index_t fortran_upper) // Original Fortran upper bound
16+
{
17+
printf("\n=== C side: c_bounder (1D) called ===\n");
18+
19+
if (a == NULL) {
20+
fprintf(stderr, "ERROR: NULL descriptor\n");
21+
return false;
22+
}
23+
24+
if (a->rank != 1) {
25+
fprintf(stderr, "ERROR: Expected rank 1, got %" PRId64 "\n", (int64_t)a->rank);
26+
return false;
27+
}
28+
29+
CFI_dim_t *dim = &a->dim[0];
30+
CFI_index_t c_lower = dim->lower_bound; // This is almost always 0
31+
CFI_index_t extent = dim->extent;
32+
CFI_index_t c_upper = c_lower + extent - 1;
33+
34+
printf("C Descriptor: lower=%td, upper=%td, extent=%td\n", c_lower, c_upper, extent);
35+
printf("Fortran Array: lower=%td, upper=%td\n", fortran_lower, fortran_upper);
36+
37+
// === Correct validation for bind(c) ===
38+
if (c_lower != 0) {
39+
fprintf(stderr, "WARNING: C lower bound is not 0 (got %td)\n", c_lower);
40+
}
41+
42+
if (extent != (fortran_upper - fortran_lower + 1)) {
43+
fprintf(stderr, "ERROR: Extent mismatch! Expected %td, got %td\n",
44+
(fortran_upper - fortran_lower + 1), extent);
45+
return false;
46+
}
47+
48+
printf("✓ Extent check PASSED\n");
49+
printf("✓ C descriptor uses 0-based lower bound (standard behavior)\n");
50+
51+
// Optional data modification
52+
if (a->base_addr && extent > 0) {
53+
float *data = (float*)a->base_addr;
54+
data[0] = 999.0f;
55+
if (extent > 1)
56+
data[extent-1] = -999.0f;
57+
}
58+
59+
printf("=== c_bounder (1D) finished ===\n\n");
60+
return true;
61+
}
62+
63+
/* ==================== 2D Version ==================== */
64+
bool c_bounder_2d(CFI_cdesc_t *a,
65+
CFI_index_t fl1, CFI_index_t fu1,
66+
CFI_index_t fl2, CFI_index_t fu2)
67+
{
68+
printf("\n=== C side: c_bounder_2d called ===\n");
69+
70+
if (a == NULL || a->rank != 2) {
71+
fprintf(stderr, "ERROR: Expected rank 2\n");
72+
return false;
73+
}
74+
75+
// Dim 1
76+
CFI_index_t extent1 = a->dim[0].extent;
77+
if (extent1 != (fu1 - fl1 + 1)) {
78+
fprintf(stderr, "ERROR: Dim1 extent mismatch\n");
79+
return false;
80+
}
81+
82+
// Dim 2
83+
CFI_index_t extent2 = a->dim[1].extent;
84+
if (extent2 != (fu2 - fl2 + 1)) {
85+
fprintf(stderr, "ERROR: Dim2 extent mismatch\n");
86+
return false;
87+
}
88+
89+
printf("✓ 2D Extent check PASSED: [%td:%td, %td:%td]\n", fl1, fu1, fl2, fu2);
90+
printf("✓ C descriptor lower bounds normalized to 0 (standard)\n");
91+
92+
if (a->base_addr) {
93+
float *data = (float*)a->base_addr;
94+
data[0] = 999.0f;
95+
}
96+
97+
printf("=== c_bounder_2d finished ===\n\n");
98+
return true;
99+
}
100+
101+
#ifdef __cplusplus
102+
}
103+
#endif

src/bounds/bounds.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#if defined(__STDC_VERSION__) && __STDC_VERSION__ < 202311L
2+
#include <stdbool.h>
3+
#endif
4+
5+
bool c_bounder(CFI_cdesc_t*, CFI_index_t, CFI_index_t);
6+
bool c_bounder_2d(CFI_cdesc_t*, CFI_index_t, CFI_index_t, CFI_index_t, CFI_index_t);

test/bounds/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
set_property(DIRECTORY PROPERTY LABELS bounds)
2+
3+
add_executable(bounds_fortran bounds.f90 ${PROJECT_SOURCE_DIR}/src/bounds/bounds.c)
4+
target_include_directories(bounds_fortran INTERFACE ${PROJECT_SOURCE_DIR}/src/bounds)
5+
add_test(NAME BoundsFortran COMMAND bounds_fortran)

test/bounds/bounds.f90

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
program test_bindc_bounds
2+
use, intrinsic :: iso_fortran_env
3+
use, intrinsic :: iso_c_binding
4+
implicit none
5+
6+
interface
7+
logical(C_BOOL) function c_bounder(a, fl, fu) bind(c, name="c_bounder")
8+
import :: c_float, c_int64_t, C_BOOL
9+
real(c_float), intent(inout) :: a(:)
10+
integer(c_int64_t), value :: fl, fu
11+
end function
12+
13+
logical(C_BOOL) function c_bounder_2d(a, fl1, fu1, fl2, fu2) bind(c, name="c_bounder_2d")
14+
import :: c_float, c_int64_t, C_BOOL
15+
real(c_float), intent(inout) :: a(:,:)
16+
integer(c_int64_t), value :: fl1, fu1, fl2, fu2
17+
end function
18+
end interface
19+
20+
call test_default_bounds()
21+
call test_nondefault_bounds()
22+
call test_multidim()
23+
call test_array_section()
24+
call test_large_array()
25+
26+
write(output_unit, '(a)') "All bounds checks passed."
27+
28+
contains
29+
30+
subroutine check_bounds(a, label, expected_l, expected_u)
31+
real, allocatable, intent(in) :: a(:)
32+
character(*), intent(in) :: label
33+
integer, intent(in) :: expected_l, expected_u
34+
integer :: l, u
35+
36+
l = lbound(a,1)
37+
u = ubound(a,1)
38+
39+
if (l /= expected_l .or. u /= expected_u) then
40+
write(error_unit, '(a,": FAIL got [",i0,":",i0,"] expected [",i0,":",i0,"]")') &
41+
label, l, u, expected_l, expected_u
42+
error stop "Fortran bounds corrupted"
43+
end if
44+
end subroutine
45+
46+
subroutine test_default_bounds()
47+
real, allocatable :: a(:)
48+
allocate(a(1:10))
49+
call check_bounds(a, "default before", 1, 10)
50+
if (.not. c_bounder(a, 1_c_int64_t, 10_c_int64_t)) then
51+
error stop "C bounder failed"
52+
end if
53+
call check_bounds(a, "default after ", 1, 10)
54+
end subroutine
55+
56+
subroutine test_nondefault_bounds()
57+
real, allocatable :: a(:)
58+
allocate(a(-3:6))
59+
call check_bounds(a, "nondefault before", -3, 6)
60+
if (.not. c_bounder(a, -3_c_int64_t, 6_c_int64_t)) then
61+
error stop "C bounder failed"
62+
end if
63+
call check_bounds(a, "nondefault after ", -3, 6)
64+
end subroutine
65+
66+
subroutine test_multidim()
67+
real, allocatable :: a(:,:)
68+
allocate(a(-2:5, 10:20))
69+
70+
if (lbound(a,1)/=-2 .or. ubound(a,1)/=5 .or. &
71+
lbound(a,2)/=10 .or. ubound(a,2)/=20) then
72+
error stop "Multidim setup failed"
73+
end if
74+
75+
if (.not. c_bounder_2d(a, -2_c_int64_t, 5_c_int64_t, 10_c_int64_t, 20_c_int64_t)) then
76+
error stop "C bounder 2D failed"
77+
end if
78+
79+
if (lbound(a,1)/=-2 .or. ubound(a,1)/=5 .or. &
80+
lbound(a,2)/=10 .or. ubound(a,2)/=20) then
81+
error stop "Multidim bounds corrupted"
82+
end if
83+
end subroutine
84+
85+
subroutine test_array_section()
86+
real, allocatable, target :: a(:)
87+
real, pointer :: p(:)
88+
89+
allocate(a(-10:20))
90+
p => a(5:15) ! pointer lower bound defaults to 1 here
91+
92+
if (lbound(p,1) /= 1 .or. ubound(p,1) /= 11) then
93+
error stop "Section setup failed"
94+
end if
95+
96+
! Pass the section (C will see 0-based)
97+
if (.not. c_bounder(p, 5_c_int64_t, 15_c_int64_t)) then
98+
error stop "C bounder failed"
99+
end if
100+
101+
! Original array should still be intact
102+
if (lbound(a,1) /= -10 .or. ubound(a,1) /= 20) then
103+
error stop "Original array corrupted via section"
104+
end if
105+
106+
print '(a)', "Array section test passed"
107+
end subroutine
108+
109+
subroutine test_large_array()
110+
real, allocatable :: a(:)
111+
allocate(a(1:1000000))
112+
if (.not. c_bounder(a, 1_c_int64_t, 1000000_c_int64_t)) then
113+
error stop "C bounder failed"
114+
end if
115+
print '(a)', "Large array test passed"
116+
end subroutine
117+
118+
end program

0 commit comments

Comments
 (0)