Skip to content

Move function/method bodies from .h -> .cpp for Func, Pipeline#5657

Closed
steven-johnson wants to merge 5 commits intomasterfrom
srj/de-inline
Closed

Move function/method bodies from .h -> .cpp for Func, Pipeline#5657
steven-johnson wants to merge 5 commits intomasterfrom
srj/de-inline

Conversation

@steven-johnson
Copy link
Copy Markdown
Contributor

As a precondition for #5655, we want to ensure that function bodies are reliably instantiated in libHalide (rather than in user code). To that end, the transformations I'm proposing:

  • All functions/methods that are part of the Halide API (or "adjacent", e.g. necessary base classes or helper functions) should have their bodies in a .cpp file, not in a .h file. This includes ctors and dtors that are marked as '=default'. This also includes even "trivial" get/set functions and the like.

  • All template classes that are part of the Halide API should be small enough to mark as HALIDE_ALWAYS_INLINE without fear of code explosion. (If/when we encounter such API that can't be refactored to be a thin layer around non-templated code, we may need to rethink this.)

  • HALIDE_NO_USER_CODE_INLINE should just go away entirely.

This specific PR does these transformations on Func and Pipeline.

As a precondition for #5655, we want to ensure that function bodies are reliably instantiated in libHalide (rather than in user code). To that end, the transformations I'm proposing:

- All functions/methods that are part of the Halide API (or "adjacent", e.g. necessary base classes or helper functions) should have their bodies in a .cpp file, not in a .h file. This includes ctors and dtors that are marked as '=default'. This also includes even "trivial" get/set functions and the like.

- All template classes that are part of the Halide API should be small enough to mark as HALIDE_ALWAYS_INLINE without fear of code explosion. (If/when we encounter such API that can't be refactored to be a thin layer around non-templated code, we may need to rethink this.)

- HALIDE_NO_USER_CODE_INLINE should just go away entirely.

This specific PR does these transformations on Func and Pipeline.
Comment thread src/Func.h
Stage &reorder(const std::vector<VarOrRVar> &vars);

template<typename... Args>
HALIDE_NO_USER_CODE_INLINE typename std::enable_if<Internal::all_are_convertible<VarOrRVar, Args...>::value, Stage &>::type
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This can't safely be inlined into user code - it causes useless line numbers in error messages. We want everything in the Halide namespace that could throw a user_error to stay in the Halide namespace, and not get pasted into user code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

useless line numbers are bad, but it's not clear to me how that causes that -- all this is doing in user code is gathering up the args into a vector, then calling the non-templated form of reorder(). Where do the useless line numbers get injected if this is inlined?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The error message reporter walks up the stack and reports the line number associated with the first line it finds outside of the Halide:: namespace to help users pinpoint what call into libHalide threw an error. If this method body gets pasted into user code and then the underlying reorder call throws an error then you get an error message blaming Halide.h:34897 instead of your_code.cpp:87

This issue was the origin of HALIDE_NO_USER_CODE_INLINE. For a bunch of functions we wanted inline linkage (because templates), but actually inlining the bodies muddies the boundary between user code and libHalide.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hm, so in that case, why not use HALIDE_NEVER_INLINE?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Digging through history:

d1a4065

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That explains the naming, but it doesn't explain why we are making the sometimes-inline vs never-inline choice.

But at a more meta level, I'm pondering whether this tradeoff is the right decision. The main advantage seems to be that when an error occurs, the user sees a much more helpful line number, so, that's good. But as a downside, it (IMHO) makes it hard to quickly reason about where this code will actually be instantiated -- what we're actually doing here is saying "attempt to inline this if used internally by halide, but never inline in user code". That's not unreasonable, but it leaves open the possibility of the internal-to-halide usage not actually being inlined (since it's not a 'force' inline), making it an exported symbol from libHalide... and now we have two versions of it seen by user code (the one from libHalide, plus the one instantiated in user code due to the no-inline directive). The linker will quietly choose one of these, but they could be subtly different due to different compilers being used. I dunno, maybe I'm overthinking this, but quiet unpredictabilities of this sort worry me.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have no problem with just making it never inline.

Comment thread src/Func.h
* disambiguate calls to min on FuncRefs when a user has pulled both
* Halide::min and std::min into their namespace. */
// @{
inline Expr min(const FuncRef &a, const FuncRef &b) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this need to stay in the header?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good question. Let me find out.

Comment thread src/Func.h

/** Construct a new Func to wrap a Buffer. */
template<typename T>
HALIDE_NO_USER_CODE_INLINE explicit Func(Buffer<T> &im)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same issue as above. We can't inline this.

Comment thread src/Pipeline.h
}

template<typename T>
HALIDE_NO_USER_CODE_INLINE RealizationArg(Buffer<T> &dst)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not sure if this one can throw an error or not. We can only use ALWAYS_INLINE if it won't.

@steven-johnson
Copy link
Copy Markdown
Contributor Author

Note that #5655 is now moot, but the goal of this PR is still worthwhile; it's on hold briefly while I attend to other things but also to think more about how to handle inlined template methods.

@alexreinking alexreinking added this to the v12.0.0 milestone Feb 13, 2021
@alexreinking alexreinking modified the milestones: v12.0.0, v13.0.0 Apr 16, 2021
@steven-johnson steven-johnson deleted the srj/de-inline branch May 13, 2021 17:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants