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

[mlir] Declare promised interfaces for all dialects #78368

Merged
merged 14 commits into from
Mar 16, 2024

Conversation

justinfargnoli
Copy link
Contributor

@justinfargnoli justinfargnoli commented Jan 17, 2024

This PR adds promised interface declarations for all interfaces declared in InitAllDialects.h.

Promised interfaces allow a dialect to declare that it will have an implementation of a particular interface, crashing the program if one isn't provided when the interface is used.

Copy link

github-actions bot commented Jan 17, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@justinfargnoli justinfargnoli self-assigned this Jan 17, 2024
@justinfargnoli justinfargnoli changed the title Jf/promise interfaces [mlir] Declare promised interfaces for all dialects Jan 28, 2024
@justinfargnoli
Copy link
Contributor Author

Open question: where should I declare promised interfaces for the builtin Dialect?

@justinfargnoli justinfargnoli changed the title [mlir] Declare promised interfaces for all dialects [mlir] Declare promised interfaces for all dialects [2/3] Jan 28, 2024
@justinfargnoli justinfargnoli force-pushed the jf/promise-interfaces branch 2 times, most recently from 22d1a01 to 41811e4 Compare January 30, 2024 04:48
@justinfargnoli justinfargnoli marked this pull request as ready for review January 30, 2024 04:59
@justinfargnoli
Copy link
Contributor Author

I am seeing some build failures with this PR when testing with ninja -t missingdeps.

@zero9178, thank you for the detailed write-up. I was only able to reproduce the following missing dependency:

Missing dep: tools/mlir/lib/Dialect/Tosa/CMakeFiles/obj.MLIRTosaDialect.dir/IR/TosaOps.cpp.obj uses tools/mlir/include/mlir/Dialect/Mesh/Interfaces/ShardingInterface.h.inc (generated by CUSTOM_COMMAND)

Add MLIRShardingInterfaceIncGen dependency should resolve that.

Please let me know if you're still able to reproduce the additional missing dependencies on your end.

@justinfargnoli
Copy link
Contributor Author

Hence my proposition to revert and have mlir/Dialect/Tensor/IR depend on mlir/Dialect/Transform/IR, which itself doesn't depend on dialect-specific transforms.

@ftynse, assuming I've correctly followed the discussion, Update include should address this.

@@ -63,6 +64,8 @@ void mlir::bufferization::BufferizationDialect::initialize() {
#include "mlir/Dialect/Bufferization/IR/BufferizationOps.cpp.inc"
>();
addInterfaces<BufferizationInlinerInterface>();
declarePromisedInterfaces<BufferizableOpInterface, func::CallOp, func::FuncOp,
func::ReturnOp>();
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be in the Func dialect I believe

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to confirm, is this reasoning incorrect?

I asked this because I wasn't sure if you were responding to the earlier discussion, but I'm realizing now that you're just responding to my ping from yesterday.

This should be in the Func dialect I believe

Thus, this sounds good to me. I'll make the change.

Copy link
Member

Choose a reason for hiding this comment

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

It is sufficient to put this in the func dialect for now, and eventually proceed with splitting out the interface from the dialect as I did with transform if feasible/desirable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

@ftynse ftynse left a comment

Choose a reason for hiding this comment

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

Overall LGTM with the bufferization/func change.

#include "mlir/Dialect/Complex/IR/Complex.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/Dialect/Transform/IR/TransformInterfaces.h"
Copy link
Member

Choose a reason for hiding this comment

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

#85221 achieves the split. It is mostly orthogonal to this patch, so no need to wait for that one to land IMO.

@@ -63,6 +64,8 @@ void mlir::bufferization::BufferizationDialect::initialize() {
#include "mlir/Dialect/Bufferization/IR/BufferizationOps.cpp.inc"
>();
addInterfaces<BufferizationInlinerInterface>();
declarePromisedInterfaces<BufferizableOpInterface, func::CallOp, func::FuncOp,
func::ReturnOp>();
Copy link
Member

Choose a reason for hiding this comment

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

It is sufficient to put this in the func dialect for now, and eventually proceed with splitting out the interface from the dialect as I did with transform if feasible/desirable.

@joker-eph joker-eph requested a review from ftynse March 14, 2024 21:08
@justinfargnoli
Copy link
Contributor Author

Assuming the tests pass and I don't receive any other comments, I'll plan on merging this over the weekend!

@justinfargnoli justinfargnoli merged commit 513cdb8 into llvm:main Mar 16, 2024
4 checks passed
@d0k
Copy link
Member

d0k commented Mar 18, 2024

Apart from the obvious layering issues this change has, it also makes it impossible to mix and match bufferization interface implementations, which users actually do.

Can we revert this? The generic interfaces seem fine, but the bufferization dialect changes seem to be fundamentally incompatible with MLIR's design and tightly couple things that shouldn't be coupled.

@joker-eph
Copy link
Collaborator

joker-eph commented Mar 18, 2024

it also makes it impossible to mix and match bufferization interface implementations

I can't figure out what do you mean by that? Can you clarify what "mix and match" refer to more precisely? Maybe give an example?

but the bufferization dialect changes seem to be fundamentally incompatible with MLIR's design and tightly couple things that shouldn't be coupled.

Can you clarify where do you see a coupling right now?

The only intended coupling is that the system should be "well setup": without promises it is just too brittle. Actually the addition of external interface was not supposed to be added without this mechanism, this has just been a ball dropped on the follow-ups there for too long.

@d0k
Copy link
Member

d0k commented Mar 18, 2024

it also makes it impossible to mix and match bufferization interface implementations, which users actually do.

What does this means?

Right now you can use parts of bufferization. Like bufferizing my custom dialect but don't bufferize all of the other MLIR dialects (which comes with its own assumptions on Tensors and MemRefs). After this change you're forced to load all the bufferization interfaces for all the ops, which breaks this use case.

but the bufferization dialect changes seem to be fundamentally incompatible with MLIR's design and tightly couple things that shouldn't be coupled.

Can you clarify where do you see a coupling right now?

It couples Tensor to Bufferization and to Transform dialect. While technically this can be made a header-only dependency (right now it's not), I really dislike this from a design standpoint. MLIR is meant to be used as parts and now there's a dependency between dialects that are meant to be independent. I like the intention of making interfaces less fragile to use, but the point of having the external interfaces in the first place is allowing us to have weaker coupling.

@joker-eph
Copy link
Collaborator

It couples Tensor to Bufferization and to Transform dialect. While technically this can be made a header-only dependency (right now it's not),

The promise declarations are meant to be header only, why is it not right now?

After this change you're forced to load all the bufferization interfaces for all the ops, which breaks this use case.

The promise only forces you to load what you use right now: are you saying that you have ops in your IR for which you want to run the pass but you want the dyn_cast<BufferizationOpIntertace>(op) to intentionally fail?
I don't quite see that as something that can (and more importantly should!) be supported. This is not the right mechanism, you need to look into the buffization pass itself, and/or inject an interface implementation that behaves as you'd expect for these operations.

@d0k
Copy link
Member

d0k commented Mar 18, 2024

It couples Tensor to Bufferization and to Transform dialect. While technically this can be made a header-only dependency (right now it's not),

The promise declarations are meant to be header only, why is it not right now?

It does depend on TypeIDs to be present.

After this change you're forced to load all the bufferization interfaces for all the ops, which breaks this use case.

The promise only forces you to load what you use right now: are you saying that you have ops in your IR for which you want to run the pass but you want the dyn_cast<BufferizationOpIntertace>(op) to intentionally fail? I don't quite see that as something that can (and more importantly should!) be supported. This is not the right mechanism, you need to look into the buffization pass itself, and/or inject an interface implementation that behaves as you'd expect for these operations.

I guess I can make it work through Bufferization's OpFilter, but that seems backwards to me. Why do I have to first load an extension interface just to not use it? Shouldn't I only load what I actually want to use?

@joker-eph
Copy link
Collaborator

I guess I can make it work through Bufferization's OpFilter, but that seems backwards to me. Why do I have to first load an extension interface just to not use it? Shouldn't I only load what I actually want to use?

If Bufferization is doing dyn_cast<BufferizationInterface>(op), then from the system point of view you are actually using it. But wouldn't OpFilter actually make it so that the dyn_cast is just not performed?

It couples Tensor to Bufferization and to Transform dialect. While technically this can be made a header-only dependency (right now it's not),

The promise declarations are meant to be header only, why is it not right now?

It does depend on TypeIDs to be present.

Right we get a weak symbol injected, but that's still header-only to me, why do you consider it's not?
I mean this seems inherent to any header-only library usage: some "code" will be present into the user, unless you're only using macros from a header of course!
Inline functions from a header only library end up with similar symbols.

@d0k
Copy link
Member

d0k commented Mar 18, 2024

I guess I can make it work through Bufferization's OpFilter, but that seems backwards to me. Why do I have to first load an extension interface just to not use it? Shouldn't I only load what I actually want to use?

If Bufferization is doing dyn_cast<BufferizationInterface>(op), then from the system point of view you are actually using it. But wouldn't OpFilter actually make it so that the dyn_cast is just not performed?

That's not what mlir/lib/Dialect/Bufferization/IR/BufferizableOpInterface.cpp does, it dyn_casts first and OpFilters second. Should that be fixed?

It couples Tensor to Bufferization and to Transform dialect. While technically this can be made a header-only dependency (right now it's not),

The promise declarations are meant to be header only, why is it not right now?

It does depend on TypeIDs to be present.

Right we get a weak symbol injected, but that's still header-only to me, why do you consider it's not? I mean this seems inherent to any header-only library usage: some "code" will be present into the user, unless you're only using macros from a header of course! Inline functions from a header only library end up with similar symbols.

I don't consider those header-only dependencies, and there are build modes where this will break. The implementation is fine to give those symbols a home. But I guess so far MLIR has been fine with it so maybe it's not a big deal.

@joker-eph
Copy link
Collaborator

joker-eph commented Mar 18, 2024

That's not what mlir/lib/Dialect/Bufferization/IR/BufferizableOpInterface.cpp does, it dyn_casts first and OpFilters second. Should that be fixed?

I would say tentatively yes based on what I understand OpFiltef to be about, but let’s ask @matthias-springer about the intended behavior for OpFilter?

I don't consider those header-only dependencies, and there are build modes where this will break.

I am interested to understand this more: what are these build modes? And why aren’t these « header-only » to you? (IIRC we crafted all this around inline functions and rely on ODR rules)

@d0k
Copy link
Member

d0k commented Mar 18, 2024

That's not what mlir/lib/Dialect/Bufferization/IR/BufferizableOpInterface.cpp does, it dyn_casts first and OpFilters second. Should that be fixed?

I would say tentatively yes based on what I understand OpFiltef to be about, but let’s ask @matthias-springer about the intended behavior for OpFilter?

I don't consider those header-only dependencies, and there are build modes where this will break.

I am interested to understand this more: what are these build modes? And why aren’t these « header-only » to you? (IIRC we crafted all this around inline functions and rely on ODR rules)

Clang's -fmodules-codegen is what I had in mind. Also windows dlls, but that's enough of a minefield not to care about.

@d0k
Copy link
Member

d0k commented Mar 18, 2024

That's not what mlir/lib/Dialect/Bufferization/IR/BufferizableOpInterface.cpp does, it dyn_casts first and OpFilters second. Should that be fixed?

I would say tentatively yes based on what I understand OpFiltef to be about, but let’s ask @matthias-springer about the intended behavior for OpFilter?

Sent #85690 that fixes the issue I'm seeing, thanks.

@stellaraccident
Copy link
Contributor

stellaraccident commented Mar 18, 2024

Personally, I think this line of work is ballooning in a way that needs a broader discussion and perhaps needs to be promoted to an ODM. I'm seeing a lot of objections and we are continuing past them with assertions that it is fine. It is possible/likely that this is a group misunderstanding of a necessarily tricky thing, but I still think that warrants discussion vs just proceeding.

@lattner

@joker-eph
Copy link
Collaborator

I haven’t seen any objection that is warranting a revert right now, but we can continue discussing at an ODM of course.

@stellaraccident
Copy link
Contributor

Then I will be more explicit, whereas I was giving a benefit of the doubt: I think this needs additional design discussion before we add a new kind of dependency edge like this to the codebase. With all thanks to those who are trying to get this done, it increases the cognitive load on all of us and I need to understand the motivations and implications more clearly.

(I'm using "I" words here but I don't think I'm the only one who is running at an understanding deficit on this)

@joker-eph
Copy link
Collaborator

we add a new kind of dependency edge like this to the codebase

You have to elaborate on what do you call "Dependency edge" exactly...

it increases the cognitive load on all of us and I need to understand the motivations and implications more clearly.

This is all a fallout from adding "external interfaces" to quickly actually.

@stellaraccident
Copy link
Contributor

This is all a fallout from adding "external interfaces" to quickly actually.

I agree with that. I think we need to take a pause and evaluate. Once added, these things live for a very long time, and I think we owe it to ourselves if we are feeling discomfort about the feature direction to step back and make sure we've got a good view of it.

This isn't a knock on anyone: these things can be really tricky to get right, and they can balloon far outside of the original conceived scope. We can stop the line on that and discuss vs just moving forward. Speaking again just for myself, I'd be willing/able to give it more principled thought but can't get my brain around it mid flight like this (without a pause/discussion).

@stellaraccident
Copy link
Contributor

You have to elaborate on what do you call "Dependency edge" exactly...

This header only "backwards" dep on the TypeID. Multiple people on this thread look at this and think it is a layering violation -- not just in some specific build system senses but in the "are we sure we want to do that" sense.

@joker-eph
Copy link
Collaborator

Assuming we have completeness on the "promises" right now, pausing on any new addition seems fine to me, but that will block anyone who has a new interface use upstream.

That is, I would object to "backslide" from a state where we enforce our invariants right now, by adding new "unsafe" interfaces registrations in tree.

@stellaraccident
Copy link
Contributor

stellaraccident commented Mar 20, 2024

I'm personally fine with us raising the bar on such architectural changes upstream. Not sure I would say it is all or nothing, but I think we have gotten somewhat ahead of ourselves on interfaces and deps among these dialects. Some back pressure forcing more communication and thought wouldn't be a bad thing, imo.

Comment on lines +74 to +77
declarePromisedInterfaces<bufferization::BufferizableOpInterface, BranchOp,
CondBranchOp>();
declarePromisedInterface<CondBranchOp,
bufferization::BufferDeallocationOpInterface>();
Copy link
Member

Choose a reason for hiding this comment

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

I share some concerns about the layering here. This seems to tightly couple bufferization with potentially unrelated dialects.

We have a pass and test downstream in IREE that started crashing after this change:

As far as I can tell, that code should not be using bufferization.

Copy link
Member

Choose a reason for hiding this comment

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

Possible false alarm on "that code should not be using bufferization" after some further debugging... but the layering here still feels inverted to me.

Copy link
Collaborator

Choose a reason for hiding this comment

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

From the backtrace, the crash you have seems actually a good showcase for "incorrect setup" detected by the promise right? You're using the bufferization interface but "forgot" to register the extension for the CF dialect as far as I can tell.

Copy link
Member

Choose a reason for hiding this comment

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

Yep, fixed the crash with iree-org/iree@24ee045

A pointer to the register functions to use in the PR description could have helped speed up that triage/fix process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants