-
Notifications
You must be signed in to change notification settings - Fork 130
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
Warn on potential data races #1541
Closed
Closed
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
46b4813
Write tests with mapped tasklets
luca-patrignani 922d5ef
Add config flag
luca-patrignani bf1b814
Check if there are intersecting memlet subsets for a given access node
luca-patrignani b928aa4
Filter only UserWarning in test_memlet_range_not_overlap_ranges
luca-patrignani 19b4a03
Update dace/config_schema.yml
luca-patrignani 653b0de
Move check_race_conditions flag into experimental category and add test
luca-patrignani 8534fb3
Create test with overlapping ranges with two different access nodes
luca-patrignani 3469315
Create test for symbolic overlap
luca-patrignani 87cf81e
Create tests for constant memlet overlap and almost overlap
luca-patrignani 8cbf45f
Revert changes to submodule
luca-patrignani 8120d39
Fix constant_memlet_almost_overlap_test
luca-patrignani 7f7f863
Fix constant_memlet_almost_overlap_test for real
luca-patrignani ff497ea
Check write-write data races and read-write data races
luca-patrignani 4b5ac97
Remove are_intesecting function
luca-patrignani 1c8d803
Validate sdfgs instead of running the programs
luca-patrignani 655fb90
Use is True instead of first checking if it's null and then check if …
luca-patrignani a849a84
Add test_elementwise_map
luca-patrignani 2f8f60c
Add test for wcr
luca-patrignani 7293fa9
Check if there are paths between the two nodes when looking for
luca-patrignani 322b256
Remove unused numpy import
luca-patrignani bacca88
Merge branch 'master' into master
phschaad File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,317 @@ | ||
# Copyright 2019-2024 ETH Zurich and the DaCe authors. All rights reserved. | ||
|
||
import warnings | ||
import dace | ||
import pytest | ||
|
||
luca-patrignani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def test_memlet_range_not_overlap_ranges(): | ||
sdfg = dace.SDFG('memlet_range_not_overlap_ranges') | ||
state = sdfg.add_state() | ||
N = dace.symbol("N", dtype=dace.int32) | ||
sdfg.add_array("A", (N//2,), dace.int32) | ||
A = state.add_access("A") | ||
sdfg.add_array("B", (N,), dace.int32) | ||
B = state.add_access("B") | ||
state.add_mapped_tasklet( | ||
name="first_tasklet", | ||
code="b = a + 10", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="k")}, | ||
map_ranges={"k": "0:N//2"}, | ||
external_edges=True, | ||
input_nodes={"A": A}, | ||
output_nodes={"B": B} | ||
) | ||
state.add_mapped_tasklet( | ||
name="second_tasklet", | ||
code="b = a - 20", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="k+N//2")}, | ||
map_ranges={"k": "0:N//2"}, | ||
external_edges=True, | ||
input_nodes={"A": A}, | ||
output_nodes={"B": B} | ||
) | ||
|
||
with warnings.catch_warnings(): | ||
warnings.simplefilter("error", UserWarning) | ||
with dace.config.set_temporary("experimental.check_race_conditions", value=True): | ||
sdfg.validate() | ||
|
||
|
||
def test_memlet_range_write_write_overlap_ranges(): | ||
sdfg = dace.SDFG('memlet_range_overlap_ranges') | ||
state = sdfg.add_state() | ||
N = dace.symbol("N", dtype=dace.int32) | ||
sdfg.add_array("A", (N,), dace.int32) | ||
A = state.add_access("A") | ||
sdfg.add_array("B", (N,), dace.int32) | ||
B = state.add_access("B") | ||
state.add_mapped_tasklet( | ||
name="first_tasklet", | ||
code="b = a + 10", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="k")}, | ||
map_ranges={"k": "0:N"}, | ||
external_edges=True, | ||
input_nodes={"A": A}, | ||
output_nodes={"B": B} | ||
) | ||
state.add_mapped_tasklet( | ||
name="second_tasklet", | ||
code="b = a - 20", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="k")}, | ||
map_ranges={"k": "0:N"}, | ||
external_edges=True, | ||
input_nodes={"A": A}, | ||
output_nodes={"B": B} | ||
) | ||
|
||
with pytest.warns(UserWarning): | ||
with dace.config.set_temporary("experimental.check_race_conditions", value=True): | ||
sdfg.validate() | ||
|
||
def test_memlet_range_write_read_overlap_ranges(): | ||
sdfg = dace.SDFG('memlet_range_write_read_overlap_ranges') | ||
state = sdfg.add_state() | ||
N = dace.symbol("N", dtype=dace.int32) | ||
sdfg.add_array("A", (N,), dace.int32) | ||
A_read = state.add_read("A") | ||
A_write = state.add_write("A") | ||
sdfg.add_array("B", (N,), dace.int32) | ||
B = state.add_access("B") | ||
sdfg.add_array("C", (N,), dace.int32) | ||
C = state.add_access("C") | ||
state.add_mapped_tasklet( | ||
name="first_tasklet", | ||
code="b = a + 10", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="k")}, | ||
map_ranges={"k": "0:N"}, | ||
external_edges=True, | ||
input_nodes={"A": A_read}, | ||
output_nodes={"B": B} | ||
) | ||
state.add_mapped_tasklet( | ||
name="second_tasklet", | ||
code="a = c - 20", | ||
inputs={"c": dace.Memlet(data="C", subset="k")}, | ||
outputs={"a": dace.Memlet(data="A", subset="k")}, | ||
map_ranges={"k": "0:N"}, | ||
external_edges=True, | ||
input_nodes={"C": C}, | ||
output_nodes={"A": A_write} | ||
) | ||
|
||
with pytest.warns(UserWarning): | ||
with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): | ||
sdfg.validate() | ||
|
||
def test_memlet_overlap_ranges_two_access_nodes(): | ||
sdfg = dace.SDFG('memlet_range_write_read_overlap_ranges') | ||
state = sdfg.add_state() | ||
N = dace.symbol("N", dtype=dace.int32) | ||
sdfg.add_array("A", (N,), dace.int32) | ||
A1 = state.add_access("A") | ||
A2 = state.add_access("A") | ||
sdfg.add_array("B", (N,), dace.int32) | ||
B1 = state.add_access("B") | ||
B2 = state.add_access("B") | ||
|
||
state.add_mapped_tasklet( | ||
name="first_tasklet", | ||
code="b = a + 10", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="k")}, | ||
map_ranges={"k": "0:N"}, | ||
external_edges=True, | ||
input_nodes={"A": A1}, | ||
output_nodes={"B": B1} | ||
) | ||
state.add_mapped_tasklet( | ||
name="second_tasklet", | ||
code="b = a - 20", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="k")}, | ||
map_ranges={"k": "0:N"}, | ||
external_edges=True, | ||
input_nodes={"A": A2}, | ||
output_nodes={"B": B2} | ||
) | ||
|
||
with pytest.warns(UserWarning): | ||
with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): | ||
sdfg.validate() | ||
|
||
def test_memlet_overlap_symbolic_ranges(): | ||
sdfg = dace.SDFG('memlet_overlap_symbolic_ranges') | ||
state = sdfg.add_state() | ||
N = dace.symbol("N", dtype=dace.int32) | ||
sdfg.add_array("A", (2*N,), dace.int32) | ||
A = state.add_access("A") | ||
sdfg.add_array("B", (2*N,), dace.int32) | ||
B = state.add_access("B") | ||
|
||
state.add_mapped_tasklet( | ||
name="first_tasklet", | ||
code="b = a + 10", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="k")}, | ||
map_ranges={"k": "0:N"}, | ||
external_edges=True, | ||
input_nodes={"A": A}, | ||
output_nodes={"B": B} | ||
) | ||
state.add_mapped_tasklet( | ||
name="second_tasklet", | ||
code="b = a - 20", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="k")}, | ||
map_ranges={"k": "0:2*N"}, | ||
external_edges=True, | ||
input_nodes={"A": A}, | ||
output_nodes={"B": B} | ||
) | ||
|
||
with pytest.warns(UserWarning): | ||
with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): | ||
sdfg.validate() | ||
|
||
def test_constant_memlet_overlap(): | ||
sdfg = dace.SDFG('constant_memlet_overlap') | ||
state = sdfg.add_state() | ||
sdfg.add_array("A", (12,), dace.int32) | ||
A = state.add_access("A") | ||
sdfg.add_array("B", (12,), dace.int32) | ||
B = state.add_access("B") | ||
|
||
state.add_mapped_tasklet( | ||
name="first_tasklet", | ||
code="b = a + 10", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="k")}, | ||
map_ranges={"k": "3:10"}, | ||
external_edges=True, | ||
input_nodes={"A": A}, | ||
output_nodes={"B": B} | ||
) | ||
state.add_mapped_tasklet( | ||
name="second_tasklet", | ||
code="b = a - 20", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="k")}, | ||
map_ranges={"k": "6:12"}, | ||
external_edges=True, | ||
input_nodes={"A": A}, | ||
output_nodes={"B": B} | ||
) | ||
|
||
with pytest.warns(UserWarning): | ||
with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): | ||
sdfg.validate() | ||
|
||
def test_constant_memlet_almost_overlap(): | ||
sdfg = dace.SDFG('constant_memlet_almost_overlap') | ||
state = sdfg.add_state() | ||
sdfg.add_array("A", (20,), dace.int32) | ||
A = state.add_access("A") | ||
sdfg.add_array("B", (20,), dace.int32) | ||
B = state.add_access("B") | ||
|
||
state.add_mapped_tasklet( | ||
name="first_tasklet", | ||
code="b = a + 10", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="k")}, | ||
map_ranges={"k": "3:10"}, | ||
external_edges=True, | ||
input_nodes={"A": A}, | ||
output_nodes={"B": B} | ||
) | ||
state.add_mapped_tasklet( | ||
name="second_tasklet", | ||
code="b = a - 20", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="k")}, | ||
map_ranges={"k": "10:20"}, | ||
external_edges=True, | ||
input_nodes={"A": A}, | ||
output_nodes={"B": B} | ||
) | ||
|
||
with warnings.catch_warnings(): | ||
warnings.simplefilter("error", UserWarning) | ||
with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): | ||
sdfg.validate() | ||
|
||
def test_elementwise_map(): | ||
sdfg = dace.SDFG('elementwise_map') | ||
state = sdfg.add_state() | ||
sdfg.add_array("A", (20,), dace.int32) | ||
A_read = state.add_read("A") | ||
A_write = state.add_write("A") | ||
|
||
state.add_mapped_tasklet( | ||
name="first_tasklet", | ||
code="aa = a + 10", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"aa": dace.Memlet(data="A", subset="k")}, | ||
map_ranges={"k": "0:20"}, | ||
external_edges=True, | ||
input_nodes={"A": A_read}, | ||
output_nodes={"A": A_write} | ||
) | ||
|
||
with warnings.catch_warnings(): | ||
warnings.simplefilter("error", UserWarning) | ||
with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): | ||
sdfg.validate() | ||
|
||
def test_memlet_overlap_with_wcr(): | ||
sdfg = dace.SDFG('memlet_overlap_with_wcr') | ||
state = sdfg.add_state() | ||
sdfg.add_array("A", (20,), dace.int32) | ||
sdfg.add_array("B", (1,), dace.int32) | ||
A = state.add_read("A") | ||
B = state.add_write("B") | ||
|
||
state.add_mapped_tasklet( | ||
name="first_reduction", | ||
code="b = a", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="0", wcr="lambda old, new: old + new")}, | ||
map_ranges={"k": "0:20"}, | ||
external_edges=True, | ||
input_nodes={"A": A}, | ||
output_nodes={"B": B} | ||
) | ||
|
||
state.add_mapped_tasklet( | ||
name="second_reduction", | ||
code="b = a", | ||
inputs={"a": dace.Memlet(data="A", subset="k")}, | ||
outputs={"b": dace.Memlet(data="B", subset="0", wcr="lambda old, new: old + new")}, | ||
map_ranges={"k": "0:20"}, | ||
external_edges=True, | ||
input_nodes={"A": A}, | ||
output_nodes={"B": B} | ||
) | ||
|
||
with warnings.catch_warnings(): | ||
warnings.simplefilter("error", UserWarning) | ||
with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): | ||
sdfg.validate() | ||
|
||
|
||
if __name__ == '__main__': | ||
test_memlet_range_not_overlap_ranges() | ||
test_memlet_range_write_write_overlap_ranges() | ||
test_memlet_range_write_read_overlap_ranges() | ||
test_memlet_overlap_ranges_two_access_nodes() | ||
test_memlet_overlap_symbolic_ranges() | ||
test_constant_memlet_overlap() | ||
test_constant_memlet_almost_overlap() | ||
test_elementwise_map() | ||
test_memlet_overlap_with_wcr() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few notes on this test:
A[i]
also writes toA[i]
, that would trigger a validation warning incorrectly. Instead you need to check if:has_path
to check that). If there is a path, then there is no conflict (read/write or write/write). This is another reason for this check being expensive. There may be some analysis passes indace.transformation.passes
that give you the paths between dependent access nodes, but creating a dictionary would be an optimization. To be safe you can usehas_path
for now and leave a comment that this could be optimized.memlet.wcr is not None
) are exempt from write-write conflicts (maybe not read-write)Please also add an elementwise map test (A->mapentry-A[i]->tasklet-A[i]->mapexit->A) just to make sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to follow your notes but I felt kind of lost, especially for point 2. Consider this two test cases:
Are they considered data races? At this moment my code raises no warnings.
I'm probably missing something important, sorry for this long PR review.