Skip to content

DArray: Fix copyto! with an empty index range past the end - #783

Merged
jpsamaroo merged 1 commit into
JuliaParallel:masterfrom
SwayamBorateIIT:fix-copyto-empty-range
Oct 23, 2026
Merged

jpsamaroo merged 1 commit into
JuliaParallel:masterfrom
SwayamBorateIIT:fix-copyto-empty-range

Conversation

@SwayamBorateIIT

@SwayamBorateIIT SwayamBorateIIT commented Oct 23, 2026 •

Copy link
Copy Markdown

darray_copyto! builds the chunk range it iterates over from the element range, as
fld1(start):fld1(stop). An empty element range does not necessarily give an empty
chunk range, and when it does not, the loop indexes outside the chunk grid:

julia> A = distribute(zeros(16, 16), Blocks(4, 4));

julia> B = distribute(ones(16, 16), Blocks(4, 4));

julia> A[18:17, 1] = @view B[18:17, 1]
ERROR: BoundsError: attempt to access 4×4 Matrix{DTask} at index [5, 1]
[...]
 [21] darray_copyto!(B::DMatrix{...}, A::DMatrix{...}, Binds::Tuple{UnitRange{Int64}, Int64}, Ainds::Tuple{UnitRange{Int64}, Int64})
    @ Dagger ~/src/Dagger.jl/src/array/copy.jl:151
 [22] copyto!
    @ ~/src/Dagger.jl/src/array/copy.jl:209 [inlined]
 [23] setindex!(::DMatrix{...}, ::SubArray{...}, ::UnitRange{Int64}, ::Int64)
    @ Dagger ~/src/Dagger.jl/src/array/indexing.jl:141

18:17 gives fld1(18, 4):fld1(17, 4) = 5:5, which is not empty, so
B.chunks[5, 1] is read from a 4x4 grid. Whether it throws depends on where the two
ends happen to land, so neighbouring cases behave differently:

destination range chunk range result
1:0, 5:4, 16:15 empty ok
17:16 5:4, empty ok
18:17, 19:18, 20:19 5:5, not empty BoundsError

This is reachable from ordinary code: LinearAlgebra's generic tril/triu fill
their result column by column with
A[max(firstindex(A,1), col-k):lastindex(A,1), col] = @view M[rows, col], and that
range is empty and starts past the end for any band offset past the corner of the
matrix.

The fix returns before the chunk ranges are computed when the destination range is
empty, since there is nothing to copy.

Testing

Added a testset covering both the previously-throwing and the previously-working
empty ranges, in rows and in columns.

What I ran locally (--test array/copyto -p 1 -t 1):

  • on this branch: 658 pass.
  • on master: 650 pass — the baseline.
  • on this branch with src/array/copy.jl reverted to master: 655 pass, 3 errored,
    on rows=18:17, rows=20:19 and cols=18:17, each the BoundsError above, with the
    five neighbouring empty ranges still passing. So the new tests fail without the fix
    rather than passing for an unrelated reason.

I have not run the full suite: the GPU and streaming files exhaust memory on my machine,
so I am relying on CI for the rest.


The change and tests were written with the help of Claude Code (Opus 5). I reviewed the
diff, reproduced the bug, and ran the tests above myself.

`darray_copyto!` derives the chunk range it iterates from the element range as
`fld1(start):fld1(stop)`. For an empty element range that is not itself empty:
`18:17` over a `Blocks(4, 4)` partitioning gives `fld1(18, 4):fld1(17, 4)`, i.e.
`5:5`, so the loop indexes row 5 of a 4x4 chunk grid and throws a `BoundsError`.
Whether it throws depends on where the two ends land, which is why the
neighbouring `17:16` (chunk range `5:4`, empty) copies fine.

Return before computing the chunk ranges when the destination range is empty.
Such a copy has nothing to do, and the generic `tril`/`triu` fallbacks reach it
for any band offset past the corner of the matrix.
@jpsamaroo
jpsamaroo merged commit e64593e into JuliaParallel:master Oct 23, 2026
39 of 59 checks passed
@jpsamaroo

Copy link
Copy Markdown
Member

Thanks so much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants