-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[SandboxIR] Add callbacks for instruction insert/remove/move ops #112965
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
Conversation
@@ -48,6 +61,21 @@ class Context { | |||
/// Type objects. | |||
DenseMap<llvm::Type *, std::unique_ptr<Type, TypeDeleter>> LLVMTypeToTypeMap; | |||
|
|||
/// Callbacks called when an IR instruction is about to get removed. Keys are | |||
/// used as IDs for deregistration. | |||
DenseMap<int, RemoveInstrCallback> RemoveInstrCallbacks; |
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.
Do you want to keep them as ints or:
using ID = int;
?
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.
Good point, thanks! Switched it to CallbackID
.
llvm/lib/SandboxIR/Context.cpp
Outdated
} | ||
|
||
void Context::runInsertInstrCallbacks(Instruction *I) { | ||
for (auto &CBEntry : InsertInstrCallbacks) { |
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.
unnecessary braces
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.
Fixed.
✅ With the latest revision this PR passed the C/C++ code formatter. |
llvm/lib/SandboxIR/Context.cpp
Outdated
@@ -660,4 +663,57 @@ Module *Context::createModule(llvm::Module *LLVMM) { | |||
return M; | |||
} | |||
|
|||
void Context::runRemoveInstrCallbacks(Instruction *I) { | |||
for (const auto &CBEntry : RemoveInstrCallbacks) { |
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.
unnecessary braces
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.
Fixed.
llvm/lib/SandboxIR/Context.cpp
Outdated
} | ||
|
||
void Context::runMoveInstrCallbacks(Instruction *I, const BBIterator &WhereIt) { | ||
for (auto &CBEntry : MoveInstrCallbacks) { |
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.
unnecessary braces
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.
Fixed.
- Introduced `CallbackID` typedef for callback ids rather than a plain int. - Remove unnecessary braces.
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.
ultra nit: no period at end of commit title
llvm/lib/SandboxIR/Context.cpp
Outdated
return ID; | ||
} | ||
void Context::unregisterInsertInstrCallback(CallbackID ID) { | ||
[[maybe_unused]] bool erased = InsertInstrCallbacks.erase(ID); |
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.
[[maybe_unused]] bool erased = InsertInstrCallbacks.erase(ID); | |
[[maybe_unused]] bool Erased = InsertInstrCallbacks.erase(ID); |
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.
done
- Updated callback (de)registration method signatures in header to use the CallbackID type instead of int. - Corrected case in some variable names to follow the style guide.
std::function<void(Instruction *, const BBIterator &)>; | ||
|
||
/// An ID for a registered callback. Used for deregistration. | ||
using CallbackID = int; |
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.
Why use an ID and not the raw pointer of the callback itself as the ID ?
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.
non-determinism
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.
Pointers to the std::function
objects stored in a map/vector can get invalidated when registering/unregistering more callbacks, right?
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.
How are we going to get non-determinism ? The ID itself is not printed anywhere, it's just used as a key to remove the callback if needed. If it's the raw pointer of the function it should still work just as well. Or am I missing something?
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.
Pointers to the std::function objects stored in a map/vector can get invalidated when registering/unregistering more callbacks, right?
True but we can get around that in multiple ways, one of which is by allocating unique pointers and getting the raw pointer.
What I don't like about the integer IDs is that:
- they are just being used as a key and they don't really convey anything else. They are not even being used for debugging purposes.
- they need the static? counter
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.
How is another layer of indirection and an extra heap allocation better than keeping an integer around?
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.
If you use raw pointers as keys in maps, you will get non-determinism.
The context should own the counter.
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.
How is another layer of indirection and an extra heap allocation better than keeping an integer around?
Yeah, it's definitely not great, and we should try to make the code that runs the callbacks as fast as possible.
Since @tschuett brought up non-determinism, should we try to make sure that the callbacks are run in the same order as registered? Because we are currently not only not getting that order but since we are iterating over the DenseMap we are also getting a non-deterministic order. Using a MapVector will fix non-determinism, but perhaps we should try to stick to registration order, and try to change it later if it turns out that it's not needed. I think we could use a vector instead of a map, because removal of a callback shouldn't happen too often (and we won't have too many callbacks) so a linear-time search should be fine. Wdyt?
EXPECT_THAT(Inserted, testing::ElementsAre(NewI)); | ||
EXPECT_THAT(Removed, testing::IsEmpty()); | ||
EXPECT_THAT(Moved, testing::IsEmpty()); | ||
|
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 drive-by comment: How about testing insertBefore and insertAfter?
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.
Discussed this offline because @vporpo's prototype only called them on create/erase, but called them insert/remove. We only need create/erase/move callbacks for the vectorizer, so we're only adding those for now, and I have renamed them to create/erase callbacks. This leaves space to add proper insert/remove callbacks in the future if we (or some future user of Sandbox IR) needs them.
Thus, no need to test those here as they have no associated callbacks for now.
Added test to check that registration order is respected when invoking registered callbacks.
- Remove insert/remove callbacks to create/erase. Don't call the erase callback on remove. This way we have a consistent model, but we don't have insert/remove callbacks, just create/erase/move. The missing callbacks can still be added if needed in the future. - Changed callback ids to uint64 in the unlikely case they could overflow a 32-bit integer in a large compile, causing hard-to-debug errors.
Please take another look. I have also switched callback ids from |
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.
Looks good to me.
I think it would make sense to add some assertions on the callback map size.
|
||
Context::CallbackID Context::registerEraseInstrCallback(EraseInstrCallback CB) { | ||
CallbackID ID = NextCallbackID++; | ||
EraseInstrCallbacks[ID] = CB; |
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.
How about an assertion on EraseInstrCallbacks.size()
checking that it's smaller than say 16 ? It could help catch issues where you keep on registering callbacks by mistake.
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.
Done. I'm not super happy with having an arbitrary limit like this not documented anywhere and with a kinda low value, but it will be useful while we're developing the vectorizer and we can always revisit this decision later.
No description provided.