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

transformations: (licm) add can_be_hoisted helper function #3078

Merged
merged 16 commits into from
Sep 28, 2024

Conversation

RRavikiran66
Copy link
Collaborator

Helper function can_be_hoisted for Loop invariant code motion. can_be_hoisted return True if the operation is invariant to be the Scf.For loop. I think the pytests can be improved a lot, I am not sure how to write pytests properly. So, I created below tests by seeing existing tests. If you can be suggest to good testcases, I will happily include those :)

Copy link

codecov bot commented Aug 21, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.05%. Comparing base (bd14181) to head (c6aab48).
Report is 109 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3078      +/-   ##
==========================================
+ Coverage   89.90%   90.05%   +0.15%     
==========================================
  Files         418      435      +17     
  Lines       52902    54647    +1745     
  Branches     8202     8473     +271     
==========================================
+ Hits        47562    49214    +1652     
- Misses       4014     4060      +46     
- Partials     1326     1373      +47     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Comment on lines 45 to 48
for region in for_op.regions:
for op in region.block.walk():
if not isinstance(op, scf.Yield):
assert can_be_hoisted(op3, region)
Copy link
Member

Choose a reason for hiding this comment

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

There's just a single region, and we have all the ops in scope, so we can just test each of the ops directly

Suggested change
for region in for_op.regions:
for op in region.block.walk():
if not isinstance(op, scf.Yield):
assert can_be_hoisted(op3, region)
assert can_be_hoisted(op1, region)
assert can_be_hoisted(op2, region)
assert can_be_hoisted(op3, region)
assert not can_be_hoisted(f, region)

This code currently does not test that calling the op with yield returns False

Comment on lines 54 to 91
ci0 = test.TestOp(result_types=[i32])
cf7 = test.TestOp(result_types=[f32])
cf8 = test.TestOp(result_types=[f32])
ci10 = test.TestOp(result_types=[i32])
ci1 = test.TestOp(result_types=[i32])
co0 = test.TestOp(result_types=[i32])
co1 = test.TestOp(result_types=[i32])
coi = test.TestOp(result_types=[i32])
block = Block(
arg_types=(
index,
index,
)
)
block1 = Block(
arg_types=(
index,
index,
)
)
region_inner = Region(block)
with ImplicitBuilder(block) as (_arg0, _):
op1 = test.TestOp(result_types=[i32])
# Test operation inside the loop
hello = test.TestOp([op1], result_types=[i32])
test.TestOp([hello], result_types=[])
# Floating-point addition inside the loop
v0 = Addf(cf7, cf8)

region = Region(block1)
with ImplicitBuilder(block1) as (_arg1, _):
scf.For(ci0, ci10, ci1, [], region_inner)

for_op = For(co0, co1, coi, [], region)
body0 = Block([cf7, cf8, for_op])
region_outer = Region(body0)
func_type = FunctionType.from_lists([], [])
function = func.FuncOp("invariant_loop_dialect", func_type, region_outer)
Copy link
Member

Choose a reason for hiding this comment

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

This is now looking great, but the structure of the module is a little hard to read. The ImplicitBuilder regions can be nested, which will make it a little easier to figure out what's going on. Here is what I would suggest to do:

Suggested change
ci0 = test.TestOp(result_types=[i32])
cf7 = test.TestOp(result_types=[f32])
cf8 = test.TestOp(result_types=[f32])
ci10 = test.TestOp(result_types=[i32])
ci1 = test.TestOp(result_types=[i32])
co0 = test.TestOp(result_types=[i32])
co1 = test.TestOp(result_types=[i32])
coi = test.TestOp(result_types=[i32])
block = Block(
arg_types=(
index,
index,
)
)
block1 = Block(
arg_types=(
index,
index,
)
)
region_inner = Region(block)
with ImplicitBuilder(block) as (_arg0, _):
op1 = test.TestOp(result_types=[i32])
# Test operation inside the loop
hello = test.TestOp([op1], result_types=[i32])
test.TestOp([hello], result_types=[])
# Floating-point addition inside the loop
v0 = Addf(cf7, cf8)
region = Region(block1)
with ImplicitBuilder(block1) as (_arg1, _):
scf.For(ci0, ci10, ci1, [], region_inner)
for_op = For(co0, co1, coi, [], region)
body0 = Block([cf7, cf8, for_op])
region_outer = Region(body0)
func_type = FunctionType.from_lists([], [])
function = func.FuncOp("invariant_loop_dialect", func_type, region_outer)
outer_region = Region([Block()])
with ImplicitBuilder(outer_region):
ci32, cf32 = test.TestOp(result_types=[i32, f32]).results
outer_for = For(ci32, ci32, ci32, [], Region(Block(arg_types=[index, index])
with ImplicitBuilder(outer_for.body) as (_outer_i, _):
inner_for = For(ci32, ci32, ci32, [], Region(Block(arg_types=[index, index])
with ImplicitBuilder(inner_for.body) as (_inner_i, _):
no_args = test.TestOp(result_types=[i32])
args_in_inner = test.TestOp([no_args], result_types=[i32])
no_results = test.TestOp([args_in_inner], result_types=[])

I removed the arith operation to avoid depending on that dialect, which is not relevant to this test. With this refactor, it is clear that the nested region functionality is not tested. It would be great to add another two testop in the loop, one of which has a region that is self-contained, the other of which depends on something outside of the for loop. I'm also not sure that the nested for loops get us anything.

Copy link
Member

Choose a reason for hiding this comment

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

In general, could you please check that each of the conditions in the can_be_hoisted function are tested? I'm not 100% sure that this is the last missing example.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have made the changes

@superlopuh superlopuh changed the title Helper function for Loop invariant code motion transformations: (licm) add can_be_hosted helper function Sep 3, 2024
@superlopuh superlopuh added the transformations Changes or adds a transformatio label Sep 3, 2024
@tobiasgrosser tobiasgrosser changed the title transformations: (licm) add can_be_hosted helper function transformations: (licm) add can_be_hoisted helper function Sep 3, 2024
Copy link
Collaborator

@PapyChacal PapyChacal left a comment

Choose a reason for hiding this comment

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

There are some possible PR improvements as mentionned by @superlopuh already, but the content now seems good to me!

Copy link
Member

@superlopuh superlopuh left a comment

Choose a reason for hiding this comment

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

Fantastic! Thank you very much for iterating on this, it was more complex than I thought

@superlopuh superlopuh merged commit 7c54f35 into main Sep 28, 2024
14 checks passed
@superlopuh superlopuh deleted the ravikiran/can_be_hoisted branch September 28, 2024 23:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
transformations Changes or adds a transformatio
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants