@@ -4185,6 +4185,71 @@ end program |
| 4185 | 4185 | let _ = std::fs::remove_file(&src); |
| 4186 | 4186 | } |
| 4187 | 4187 | |
| 4188 | +#[test] |
| 4189 | +fn allocatable_rank2_assign_from_transpose_uses_column_major_stride() { |
| 4190 | + // F2018 §10.1.5: allocatable LHS = transpose(reshape(...)) reallocates |
| 4191 | + // dest with source's shape and copies the data. afs_assign_allocatable |
| 4192 | + // used to set dest.dims[i].stride = 1 across the board, but the |
| 4193 | + // descriptor convention is that stride encodes per-dim *memory step* |
| 4194 | + // in column-major order — see afs_create_section's matching note. |
| 4195 | + // With stride=(1,1) on a 3x3 contiguous block, any subsequent path |
| 4196 | + // that walked the descriptor (e.g. ALLOCATE(target, source=A) inside |
| 4197 | + // a call where A is the assumed-shape dummy) produced overlapping |
| 4198 | + // byte offsets and corrupted the copy. Surfaced as wrong eigenvalue |
| 4199 | + // matrices in stdlib's eigvals/eig clusters. |
| 4200 | + let src = write_program( |
| 4201 | + r#" |
| 4202 | +program p |
| 4203 | + implicit none |
| 4204 | + real, allocatable :: A(:,:) |
| 4205 | + A = transpose(reshape([2.0, 8.0, 4.0, 1.0, 3.0, 5.0, 9.0, 5.0, -2.0], [3,3])) |
| 4206 | + call check(A) |
| 4207 | +contains |
| 4208 | + subroutine check(a) |
| 4209 | + real, intent(in), target :: a(:,:) |
| 4210 | + real, allocatable :: amat(:,:) |
| 4211 | + allocate(amat(3,3), source=a) |
| 4212 | + if (abs(amat(1,1) - 2.0) > 1.0e-6) error stop 1 |
| 4213 | + if (abs(amat(2,1) - 1.0) > 1.0e-6) error stop 2 |
| 4214 | + if (abs(amat(3,1) - 9.0) > 1.0e-6) error stop 3 |
| 4215 | + if (abs(amat(1,2) - 8.0) > 1.0e-6) error stop 4 |
| 4216 | + if (abs(amat(2,2) - 3.0) > 1.0e-6) error stop 5 |
| 4217 | + if (abs(amat(3,2) - 5.0) > 1.0e-6) error stop 6 |
| 4218 | + if (abs(amat(1,3) - 4.0) > 1.0e-6) error stop 7 |
| 4219 | + if (abs(amat(2,3) - 5.0) > 1.0e-6) error stop 8 |
| 4220 | + if (abs(amat(3,3) - (-2.0)) > 1.0e-6) error stop 9 |
| 4221 | + print *, 'ok' |
| 4222 | + end subroutine |
| 4223 | +end program |
| 4224 | +"#, |
| 4225 | + "f90", |
| 4226 | + ); |
| 4227 | + let out = unique_path("rank2_assign_transpose_stride", "bin"); |
| 4228 | + let compile = Command::new(compiler("armfortas")) |
| 4229 | + .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()]) |
| 4230 | + .output() |
| 4231 | + .expect("rank2-stride compile failed"); |
| 4232 | + assert!( |
| 4233 | + compile.status.success(), |
| 4234 | + "should compile: {}", |
| 4235 | + String::from_utf8_lossy(&compile.stderr) |
| 4236 | + ); |
| 4237 | + let run = Command::new(&out).output().expect("run failed"); |
| 4238 | + assert!( |
| 4239 | + run.status.success(), |
| 4240 | + "rank-2 allocatable = transpose() produced wrong values: status={:?} stderr={}", |
| 4241 | + run.status, |
| 4242 | + String::from_utf8_lossy(&run.stderr) |
| 4243 | + ); |
| 4244 | + assert!( |
| 4245 | + String::from_utf8_lossy(&run.stdout).contains("ok"), |
| 4246 | + "expected 'ok': {}", |
| 4247 | + String::from_utf8_lossy(&run.stdout) |
| 4248 | + ); |
| 4249 | + let _ = std::fs::remove_file(&out); |
| 4250 | + let _ = std::fs::remove_file(&src); |
| 4251 | +} |
| 4252 | + |
| 4188 | 4253 | #[test] |
| 4189 | 4254 | fn runtime_shape_local_uses_column_major_stride_for_row_section_assign() { |
| 4190 | 4255 | // F2018 §6.5.3.2: Fortran arrays are stored in column-major order. |