Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add/improve errors for .localAccess() calls to non-local elements #25754

Merged
merged 20 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
22618dd
Add .localAccess() errors for remote single-locale arrays
bradcray Aug 14, 2024
4128a23
Refactor to support better errors for block-distributed arrays as well
bradcray Aug 14, 2024
f55e69d
Change localCheck to a param to avoid module init circularities
bradcray Aug 16, 2024
b71e027
Extend locality checks to handle arrays with multiple blocks per locale
bradcray Aug 16, 2024
ef3e202
Skip new tests for a single locale; they're designed for multiple
bradcray Aug 16, 2024
1175602
Merge branch 'main' of https://github.com/chapel-lang/chapel into loc…
bradcray Sep 4, 2024
34a636e
Fix localSubdomains() calls for main distributions in the face of dup…
bradcray Sep 6, 2024
adabcef
Rework local access checks to permit a new optional interface for Ste…
bradcray Sep 6, 2024
802c72a
Merge branch 'main' of https://github.com/chapel-lang/chapel into loc…
bradcray Sep 7, 2024
9a30dd7
Update a sensitive-to-uid changes .bad entries
bradcray Sep 7, 2024
12f63a0
Remove trailing spaces
bradcray Sep 9, 2024
5df5a4a
Clean up skipifs and generalize for --no-local testing
bradcray Sep 9, 2024
266fb3f
Update test to work for comm=none as well; add more working + error c…
bradcray Sep 9, 2024
435c1bf
Clean up localAccess indexing expressions for 1D and nD arrays
bradcray Sep 9, 2024
f7270bd
Clean up / simplify code and diff
bradcray Sep 9, 2024
f76bf02
Act on part of Engin's feedback
bradcray Sep 10, 2024
24b927e
Add illegal local access back in to capture error
bradcray Sep 10, 2024
629421f
Update stencil oversubscription test for multiple locales
bradcray Sep 10, 2024
05ec799
Add --checks flags to localAccess error test cases
bradcray Sep 10, 2024
2f9db17
With new error-generating localAccess cases added, specialize .goods …
bradcray Sep 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions modules/dists/BlockDist.chpl
Original file line number Diff line number Diff line change
Expand Up @@ -1917,6 +1917,12 @@ proc BlockImpl.chpl__locToLocIdx(loc: locale) {
return (false, targetLocDom.first);
}

iter BlockImpl.chpl__locToLocIdxs(loc: locale) {
for locIdx in targetLocDom do
if (targetLocales[locIdx] == loc) then
yield locIdx;
}

// Block subdomains are continuous

proc BlockArr.dsiHasSingleLocalSubdomain() param do return !allowDuplicateTargetLocales;
Expand Down Expand Up @@ -1953,6 +1959,17 @@ proc BlockDom.dsiLocalSubdomain(loc: locale) {
return d;
}
}
iter BlockDom.dsiLocalSubdomains(loc: locale) {
for locid in dist.chpl__locToLocIdxs(loc) {
var inds = chpl__computeBlock(locid, dist.targetLocDom, dist.boundingBox, dist.boundingBox.dims());
yield whole[(...inds)];
Copy link
Contributor

Choose a reason for hiding this comment

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

I am surprised by this implementation. Shouldn't it be just yield this.locDoms[locid].myBlock;?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, good question. I took this approach due to the else clause in the dsiLocalSubdomain() routine just above, and to make it work with oversubscription; but in retrospect, I think you're right that I over-engineered it. Specifically, I believe I was thinking that myBlock would only work in a non-oversubscribed environment, but with the loop over locids, I think you're probably right. I'll check that out to make sure that it works and doesn't introduce communication.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ooh, and I can update the tests to do the localAcesss calls within local blocks as a way to both verify this and lock it in going forward.

Copy link
Member Author

Choose a reason for hiding this comment

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

But then in my final review, I realized that the locale argument was never used / always defaulted to here, so removed that logic. So again, I think you're right.

Ah wait, it is necessary (if regrettable from a complexity standpoint) to do here since this routine takes a locale argument, so may be answering local subdomain queries about a remote locale's ownership, and we don't want to communicate with that remote locale to answer the question.

We could simplify / streamline the (common) locale == here case by either putting a conditional into this loop to check for it, or by creating two overloads of the routine, one which takes no argument (defaulting to here) and the other which doesn't—then updating ChapelArray.chpl's code to similarly split the implementation into two versions, each of which calls the corresponding one here. I don't feel strongly inclined to do either of those things in this PR because I worry about keeping the two implementations in sync and would need to add more testing to distinguish between the remote and local cases. It also feels a little like a change to the public interface (though it's one that I think would be acceptable since it wouldn't break existing code); or we could keep the same public interface and put a conditional into the outer call, but that doesn't seem great either.

That said, the StencilDom.doiLocalStoredSubdomains case should not be as expensive as I was fearing in the comment you commented on now that we don't accept a locale argument in that iterator, so that comment does need to be updated (and I can add a locality check to that test to lock that in as well).

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 I see. I agree that the solution you outlined would be good, but shouldn't be a part of this PR.

}
}
iter BlockArr.dsiLocalSubdomains(loc: locale) {
for subdoms in dom.dsiLocalSubdomains(loc) {
yield subdoms;
}
}

proc BlockDom.numRemoteElems(viewDom, rlo, rid) {
// NOTE: Not bothering to check to see if rid+1, length, or rlo-1 used
Expand Down
17 changes: 17 additions & 0 deletions modules/dists/CyclicDist.chpl
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,12 @@ proc CyclicImpl.chpl__locToLocIdx(loc: locale) {
return (false, targetLocDom.first);
}

iter CyclicImpl.chpl__locToLocIdxs(loc: locale) {
for locIdx in targetLocDom do
if (targetLocs[locIdx] == loc) then
yield locIdx;
}

proc CyclicImpl.getChunk(inds, locid) {
const chunk = locDist(locid).myChunk((...inds.getIndices()));
return chunk;
Expand Down Expand Up @@ -1621,6 +1627,17 @@ proc CyclicDom.dsiLocalSubdomain(loc: locale) {
return d;
}
}
iter CyclicDom.dsiLocalSubdomains(loc: locale) {
for locid in dist.chpl__locToLocIdxs(loc) {
var inds = chpl__computeCyclic(idxType, locid, dist.targetLocDom.dims(), dist.startIdx);
yield whole[(...inds)];
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto

}
}
iter CyclicArr.dsiLocalSubdomains(loc: locale) {
for subdoms in dom.dsiLocalSubdomains(loc) {
yield subdoms;
}
}

proc CyclicArr.canDoOptimizedSwap(other) {
var domsMatch = true;
Expand Down
37 changes: 37 additions & 0 deletions modules/dists/StencilDist.chpl
Original file line number Diff line number Diff line change
Expand Up @@ -2313,6 +2313,12 @@ proc StencilImpl.chpl__locToLocIdx(loc: locale) {
return (false, targetLocDom.first);
}

iter StencilImpl.chpl__locToLocIdxs(loc: locale) {
for locIdx in targetLocDom do
if (targetLocales[locIdx] == loc) then
yield locIdx;
}

// Stencil subdomains are continuous

proc StencilArr.dsiHasSingleLocalSubdomain() param do return !allowDuplicateTargetLocales;
Expand Down Expand Up @@ -2346,6 +2352,37 @@ proc StencilDom.dsiLocalSubdomain(loc: locale) {
return d;
}
}
iter StencilDom.dsiLocalSubdomains(loc: locale) {
for locid in dist.chpl__locToLocIdxs(loc) {
var inds = chpl__computeBlock(locid, dist.targetLocDom, dist.boundingBox, dist.boundingBox.dims());
yield whole[(...inds)];
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto

}
}
iter StencilArr.dsiLocalSubdomains(loc: locale) {
for subdoms in dom.dsiLocalSubdomains(loc) {
yield subdoms;
}
}

// These are similar to the normal dsiLocalSubdomains() itertors, but
// they yield the local blocks as extended by the fluff for the sake
// of locality-checking rather than just the local blocks.
//
iter StencilDom.doiLocalStoredSubdomains() {
for locid in dist.chpl__locToLocIdxs(here) {

// Could calculate the indices directly, as in the normal
// subdomains case above, but that's more complicated for
// StencilDist, and this isn't on performance-critical paths
// anyway, so I'm just looking it up.
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment confuses me. In other instances, you are doing some math followed by a domain slice. Looking it up feels like the more performant and maintainable implementation.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, there's one other piece of history here that I was forgetting, but that this comment reminds me of. I originally wrote these iterators to take a locale argument, and didn't want to do communication to answer the question. But then in my final review, I realized that the locale argument was never used / always defaulted to here, so removed that logic. So again, I think you're right.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've now updated the comment to indicate that this is guaranteed to be local since we're only ever making this query for here (whereas the traditional localSubdomains() queries can be called with any locale argument to determine what subdomains are local to a given locale).

yield locDoms[locid].myFluff;
}
}
iter StencilArr.doiLocalStoredSubdomains() {
for subdom in dom.doiLocalStoredSubdomains() do {
yield subdom;
}
}

proc StencilDom.numRemoteElems(viewDom, rlo, rid) {
// NOTE: Not bothering to check to see if rid+1, length, or rlo-1 used
Expand Down
49 changes: 45 additions & 4 deletions modules/internal/ChapelArray.chpl
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,7 @@ module ChapelArray {
pragma "insert line file info"
pragma "always propagate line file info"
@chpldoc.nodoc
proc checkAccess(indices, value) {
proc checkAccess(indices, value, param ensureLocal=false) {
if this.isRectangular() {
if !value.dsiBoundsCheck(indices) {
if rank == 1 {
Expand Down Expand Up @@ -882,6 +882,29 @@ module ChapelArray {
"note: ", dimstr);
}
}
if ensureLocal {
const locInds = this.chpl__localStoredSubdomains(),
locIdx = || reduce for blk in locInds do blk.contains(indices);

if !locIdx {
if chpl_isNonDistributedArray() {
halt("Cannot use .localAccess() from locale ", here.id,
" on a remote array stored on locale ", value.locale.id);
} else {
if locInds.size == 1 && locInds[0].size == 0 {
halt("Call to .localAccess() from locale ", here.id,
" is illegal because it has no local array elements");
} else {
halt("Call to .localAccess",
if indices.size == 1 then "(" + indices(0):string + ")"
else indices,
" from locale ", here.id,
" refers to remote data (local indices are: ", locInds,
")");
}
}
}
}
}
}

Expand Down Expand Up @@ -1031,7 +1054,7 @@ module ChapelArray {
{
const value = _value;
if boundsChecking then
checkAccess(i, value=value);
checkAccess(i, value=value, ensureLocal=true);

if logAllArrEltAccess ||
(logDistArrEltAccess && !chpl_isNonDistributedArray()) then
Expand All @@ -1052,7 +1075,7 @@ module ChapelArray {
{
const value = _value;
if boundsChecking then
checkAccess(i, value=value);
checkAccess(i, value=value, ensureLocal=true);

if logAllArrEltAccess ||
(logDistArrEltAccess && !chpl_isNonDistributedArray()) then
Expand All @@ -1072,7 +1095,7 @@ module ChapelArray {
{
const value = _value;
if boundsChecking then
checkAccess(i, value=value);
checkAccess(i, value=value, ensureLocal=true);

if logAllArrEltAccess ||
(logDistArrEltAccess && !chpl_isNonDistributedArray()) then
Expand Down Expand Up @@ -1615,6 +1638,24 @@ module ChapelArray {
}
}

// This internal iterator is like 'localSubdomains()' but gives a
// distribution the option of indicating that it stores more
// indices than just those in its local subdomain; The stencil
// distribution is such an example since each locale stores cached
// boundary values ("fluff") in addition to its true local
// subdomain. Thus, locality checks should pass if they're within
// the fluff even though that's outside of the normal
// localSubdomain() index set.
@chpldoc.nodoc
iter chpl__localStoredSubdomains() {
use Reflection;
if canResolveMethod(_value, "doiLocalStoredSubdomains") {
for d in _value.doiLocalStoredSubdomains() do yield d;
} else {
for d in localSubdomains() do yield d;
}
}

proc chpl__isDense1DArray() param {
return this.isRectangular() &&
this.rank == 1 &&
Expand Down
2 changes: 2 additions & 0 deletions test/arrays/locality/localAccess.skipif
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CHPL_COMM==none
COMPOPTS <= --no-local
2 changes: 2 additions & 0 deletions test/arrays/locality/localAccess/COMPOPTS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-sPOD=true
-sPOD=false
1 change: 1 addition & 0 deletions test/arrays/locality/localAccess/NUMLOCALES
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2
29 changes: 29 additions & 0 deletions test/arrays/locality/localAccess/localAccess-block.chpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use BlockDist;
config param POD = true;
config const read = true;

var Arr = createArray();

on Locales[numLocales-1] {
if read then writeln(Arr.localAccess[0]); else Arr.localAccess[0] += 1;
}

proc createArray() {
const D = {0..2} dmapped new blockDist({0..99});
var A: [D] if POD then int else R;
if POD then
A = [1, 2, 3];
else
A = [new R(1), new R(2), new R(3)];
return A;
}

record R {
var x: int;

operator +(ref lhs: R, rhs: int) {
lhs.x += rhs;
return lhs;
}
}

2 changes: 2 additions & 0 deletions test/arrays/locality/localAccess/localAccess-block.execopts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--read=true
--read=false
1 change: 1 addition & 0 deletions test/arrays/locality/localAccess/localAccess-block.good
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
localAccess-block.chpl:8: error: halt reached - Call to .localAccess() from locale 1 is illegal because it has no local array elements
29 changes: 29 additions & 0 deletions test/arrays/locality/localAccess/localAccess-block2-2D.chpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use BlockDist;
config param POD = true;
config const read = true;

var Arr = createArray();

on Locales[numLocales-1] {
if read then writeln(Arr.localAccess[0,0]); else Arr.localAccess[0,0] += 1;
}

proc createArray() {
const D = {0..99 by 50, 0..99 by 50} dmapped new blockDist({0..99, 0..99});
var A: [D] if POD then int else R;
if POD then
A = reshape([1, 2, 3, 4], A.domain);
else
A = reshape([new R(1), new R(2), new R(3), new R(4)], A.domain);
return A;
}

record R {
var x: int;

operator +(ref lhs: R, rhs: int) {
lhs.x += rhs;
return lhs;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--read=true
--read=false
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
localAccess-block2-2D.chpl:8: error: halt reached - Call to .localAccess(0, 0) from locale 1 refers to remote data (local indices are: {50..99 by 50, 0..99 by 50})
29 changes: 29 additions & 0 deletions test/arrays/locality/localAccess/localAccess-block2.chpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use BlockDist;
config param POD = true;
config const read = true;

var Arr = createArray();

on Locales[numLocales-1] {
if read then writeln(Arr.localAccess[0]); else Arr.localAccess[0] += 1;
}

proc createArray() {
const D = {0..99 by 34} dmapped new blockDist({0..99});
var A: [D] if POD then int else R;
if POD then
A = [1, 2, 3];
else
A = [new R(1), new R(2), new R(3)];
return A;
}

record R {
var x: int;

operator +(ref lhs: R, rhs: int) {
lhs.x += rhs;
return lhs;
}
}

2 changes: 2 additions & 0 deletions test/arrays/locality/localAccess/localAccess-block2.execopts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--read=true
--read=false
1 change: 1 addition & 0 deletions test/arrays/locality/localAccess/localAccess-block2.good
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
localAccess-block2.chpl:8: error: halt reached - Call to .localAccess(0) from locale 1 refers to remote data (local indices are: {50..99 by 34 align 0})
26 changes: 26 additions & 0 deletions test/arrays/locality/localAccess/localAccess.chpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
config param POD = true;

config const read = true;

var Arr = createArray();

on Locales[numLocales-1] {
if read then writeln(Arr.localAccess[0]); else Arr.localAccess[0] += 1;
}

proc createArray() {
if POD then
return [1, 2, 3];
else
return [new R(1), new R(2), new R(3)];
}

record R {
var x: int;

operator +(ref lhs: R, rhs: int) {
lhs.x += rhs;
return lhs;
}
}

2 changes: 2 additions & 0 deletions test/arrays/locality/localAccess/localAccess.execopts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--read=true
--read=false
1 change: 1 addition & 0 deletions test/arrays/locality/localAccess/localAccess.good
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
localAccess.chpl:8: error: halt reached - Cannot use .localAccess() from locale 1 on a remote array stored on locale 0
2 changes: 1 addition & 1 deletion test/distributions/block/duplicateTargetLocales.allow.good
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
1 0 0 0 0 0 0 0 0 10
1 0 0 0 0 0 0 0 0 10
1 0 0 0 0 0 7 0 0 0
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not following the motivation for the changes in duplicateTargetLocales tests. I don't have a reason to oppose them either.

However, if I am following the diff correctly, there is no test for the error message of illegal localAccess in an oversubscribed distribution. If that's the case, could you add something simple for that?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, you're correct. We were permitting illegal localAccesses in oversubscribed cases (and still do with checks off), and you're also correct that adding a test of that would be good. Perhaps I can just add it to the end of these tests.

This also makes me realize: I'll need to skipif these tests with --fast and --no-checks compilations, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or, just stick a --checks in their compopts, which feels a bit easier and somewhat better as the nature of this test will now become also about testing the --checks behavior. I don't have any strong opinions here, though.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh yeah, thanks for the reminder. I don't do this nearly often enough…

Are you confident that compopts will follow the --fast thrown by nightly testing? (that is, that I don't need to use a lastcompopts to get it in the right place?). I suspect you use this often enough that you are…

Copy link
Member Author

Choose a reason for hiding this comment

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

I think I've just verified that compopts is sufficient…

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

1 3 6 10 15 21 28 36 45 55
2 changes: 1 addition & 1 deletion test/distributions/block/duplicateTargetLocales.chpl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ writeln(A);

A = 0;
A.localAccess[1] = 1;
A.localAccess[n] = n;
A.localAccess[7] = 7;
writeln(A);

A = space;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
duplicateTargetLocales.chpl:23: warning: scan has been serialized (see issue #12482)
1 0 0 0 0 0 0 0 0 10
1 0 0 0 0 0 0 0 0 10
1 0 0 0 0 0 0 0 9 0
1 3 6 10 15 21 28 36 45 55
2 changes: 1 addition & 1 deletion test/distributions/cyclic/duplicateTargetLocales.chpl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ writeln(A);

A = 0;
A.localAccess[1] = 1;
A.localAccess[n] = n;
A.localAccess[9] = 9;
writeln(A);

A = space;
Expand Down
21 changes: 21 additions & 0 deletions test/distributions/stencil/localAccess.chpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use StencilDist;

const D = {1..10} dmapped new stencilDist({1..10}, fluff=(1,));
var A: [D] real = [i in D] i;

A.updateFluff();

// on a 2-locale run, these accesses would be considered out-of-bounds
// with respect to the indices that the locale is the primary owner
// of, but not with respect to the fluff values that it stores. This
// test ensures that we don't get an OOB access check on such local
// accesses to fluff values.

writeln(A.localAccess(5)); // this is properly owned by me
writeln(A.localAccess(6)); // this is part of my fluff

on Locales.last {
writeln(A.localAccess(6)); // this is properly owned by me
writeln(A.localAccess(5)); // this is part of my fluff
writeln(A.localAccess(4)); // this is not local
}
Loading