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

Remove for loop trick for inlining functions w/o multiple returns + optims #1097

Merged

Conversation

SteveBronder
Copy link
Contributor

Summary

This PR removes for loops surrounding inlined functions if the inlined function does not have more than one return statement while also allow C++ to perform move semantics from the inlined functions inner return to it's outer return object.

After @WardBrian posted #1096 I thought it was odd that inlined functions are always wrapped in a for loop with breaks at the bottom. So this PR looks at a function as we are inlining it and if the function only has one return statement then the inlined function's code is placed into a Block (curly braces) instead of a for loop. For instance the following code is wrapped inside of a for loop that just runs once.

        double inline_sym1__;
        for (int inline_sym3__ = 1; inline_sym3__ <= 1; ++inline_sym3__) {
          inline_sym1__ = (blah(1.0, pstream__) + 0.5);   
          break;
        }

But we can see that there are not multiple returns here and so instead of having a for loop we can just have a block

        double inline_sym1__;
        {
          inline_sym1__ = (blah(1.0, pstream__) + 0.5);   
        }

After cleaning that up, I noticed that the inlining code did a pattern where an variable is declared outside of the block scope, than another variable is declared at the inlined functions scope, and at the end of the inlined functions scope the inner variable is assigned to the outer variable. The below is pseudocode showing what I mean here.

Eigen::Matrix<local_scalar_t__, -1, -1> A_outer;
{
  Eigen::Matrix<local_scalar_t__, -1, -1> A_inner;
  // Do a bunch of stuff with A_inner ...
  assign(A_outer, A_inner);
}

This is the exact kind of place where we want to do move semantics, where at the end of the scope instead of copying A_inner to A_outer we can just hand A_outer the memory of A_inner. This is safe and good because A_inner will be deleted at the end of the inlined functions block scope. To let the C++ know an expression can be moved I made an internal fun_kind called FnMove where if an expression is wrapped in a FnMove the C++ code generator can then call std::move(expr) such as the below.

Eigen::Matrix<local_scalar_t__, -1, -1> A_outer;
{
  Eigen::Matrix<local_scalar_t__, -1, -1> A_inner;
  // Do a bunch of stuff with A_inner ...
  assign(A_outer, std::move(A_inner));
}

While I was reading this PR I was having a hard time following which inline_sym{number}__ was doing what and where. So now when we generate code for inline functions the local inlined variables have the pattern inline_{function}_{orig_name}_sym{number}__. The outer return mentioned above now has the name inline_{func_name}_return_sym{number}__. The iterator for the for loop trick when we have multiple returns now has the pattern inline_{func_name}_{iterator}_sym{number}__.

Submission Checklist

  • Run unit tests
  • Documentation
    • If a user-facing facing change was made, the documentation PR is here:
    • OR, no user-facing changes were made

Release notes

Replace this text with a short note on what will change if this pull request is merged. This will be included in the release notes.

Copyright and Licensing

By submitting this pull request, the copyright holder is agreeing to
license the submitted work under the BSD 3-clause license (https://opensource.org/licenses/BSD-3-Clause)

Copy link
Member

@WardBrian WardBrian left a comment

Choose a reason for hiding this comment

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

A few comments questions. The test output on this looks a lot cleaner than the existing stuff!

src/middle/Internal_fun.ml Outdated Show resolved Hide resolved
src/analysis_and_optimization/Optimize.ml Outdated Show resolved Hide resolved
src/analysis_and_optimization/Optimize.ml Outdated Show resolved Hide resolved
src/analysis_and_optimization/Optimize.ml Outdated Show resolved Hide resolved
@SteveBronder
Copy link
Contributor Author

Also fyi before we merge this I think I should turn it on for all of the tests that get compiled and run it once through Jenkins to help ensure there are no weird edge cases we are missing

@WardBrian
Copy link
Member

Do you want this in before the RC? If it can wait we have tons of time to do that

@SteveBronder
Copy link
Contributor Author

It can absolutely wait. Yeah thinking it over that would be good to do. Though I may need some help as that sort of schema is a little over what I'm used to doing

@SteveBronder SteveBronder force-pushed the fix/remove-inline-func-for-loops branch from dedf6bc to ec69d87 Compare March 12, 2022 01:52
@WardBrian
Copy link
Member

Is there a reason to update Core? We can, but I’ll need to do another windows port and our CI will need updates

@SteveBronder
Copy link
Contributor Author

No we don't as long as you can tell me how to downgrade the stuff on my laptop after running opam upgrade lol. That's the only reason these are updated

@WardBrian
Copy link
Member

You should be able to do opam pin core_kernel=0.14.2

@SteveBronder
Copy link
Contributor Author

opam pin add core_kernel v0.14.2 did the trick! I think we should add that and opam pin add fmt 0.8.8 to the setup scripts

@WardBrian
Copy link
Member

We could, though I think in general there is no reason to run opam upgrade in the switch you use for Stan. Effectively everything is pinned

Copy link
Member

@WardBrian WardBrian left a comment

Choose a reason for hiding this comment

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

A few style/organization comments. I think that the logic here is sound in general, but I'd appreciate some clarifying comments in the code, especially around the sized 0 sizedtypes created.

src/analysis_and_optimization/Mem_pattern.ml Show resolved Hide resolved
src/analysis_and_optimization/Optimize.ml Outdated Show resolved Hide resolved
src/analysis_and_optimization/Optimize.ml Outdated Show resolved Hide resolved
@SteveBronder
Copy link
Contributor Author

Ty for looking this over! lmk if you have more Qs or suggestions

@WardBrian
Copy link
Member

How would you feel about doing some git squashing to remove some of the early commits in this PR (things like the opam switch that you reverted)?

Definitely optional but it's a fair number of commits which don't really meaningfully contribute to the history IMO

@WardBrian WardBrian added cleanup Code simplification or clean-up optimization labels Apr 22, 2022
Copy link
Member

@WardBrian WardBrian left a comment

Choose a reason for hiding this comment

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

This looks good to me. I propose squashing and merging rather than just a normal merge, but I leave that up to you @SteveBronder

@SteveBronder
Copy link
Contributor Author

Sounds good! I'll merge on Monday

@WardBrian WardBrian merged commit fd75a92 into stan-dev:master Apr 25, 2022
@SteveBronder SteveBronder deleted the fix/remove-inline-func-for-loops branch April 26, 2022 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cleanup Code simplification or clean-up optimization
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants