Fix inline insert spill and Miri UB - #8
Conversation
Summary of ChangesHello @ihciah, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request primarily focuses on enhancing the robustness and correctness of the Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Pull request overview
This PR fixes two issues: (1) a bug where inserting an existing key when inline storage is full would incorrectly spill to heap instead of updating in-place, and (2) Miri undefined behavior caused by SIMD operations reading uninitialized control bytes beyond the valid array bounds.
Changes:
- Added early check in
SmallMap::insertto update existing keys in-place when inline storage is full, preventing unnecessary spill to heap - Removed
Bucketabstraction and replaced bucket-based operations with direct index-based access - Introduced
init-ctrlfeature flag and conditional padding toAlignedGroupsto eliminate UB from SIMD reads - Updated generic parameters throughout to accommodate the new
LINEAR_THRESHOLDconst generic and separate inline/heap hashers
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib.rs | Adds check for existing key before spilling to heap when inline storage is full; reduces fuzzing iterations under Miri; adds test for the fix |
| src/inline.rs | Removes Bucket abstraction in favor of direct indexing; adds AlignedGroups::new() with conditional initialization; refactors ctrl byte access with separate read/write methods; updates all operations to use indices instead of Buckets |
| src/raw/util.rs | Removes Bucket struct and related functions (from_base_index, to_base_index, etc.); removes IS_ZERO_SIZED constant and invalid_mut pointer functions |
| src/serde.rs | Updates Serialize and Deserialize implementations to include new generic parameters (SH, SI, LINEAR_THRESHOLD) and uses with_capacity_and_hashers |
| README.md | Documents the new init-ctrl feature flag and its trade-offs |
| Cargo.toml | Adds init-ctrl feature flag definition |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Inserts a new element into the table in the given slot, and returns its | ||
| /// raw bucket. |
There was a problem hiding this comment.
The function documentation states it "returns its raw bucket" but the function signature shows it returns nothing (void). This documentation should be updated to remove the mention of returning a bucket, as the Bucket type has been removed in this refactoring.
| /// Inserts a new element into the table in the given slot, and returns its | |
| /// raw bucket. | |
| /// Inserts a new element into the table in the given slot. |
| #[inline] | ||
| unsafe fn insert_in_slot(&mut self, hash: u64, slot: InsertSlot, value: T) -> Bucket<T> { | ||
| unsafe fn insert_in_slot(&mut self, hash: u64, slot: InsertSlot, value: T) { | ||
| // SAFETY: The caller must uphold the safety rules for the [`RawTableInner::set_ctrl_h2`] |
There was a problem hiding this comment.
This safety comment references "RawTableInner::set_ctrl_h2" which appears to be copied from hashbrown's codebase but doesn't apply to this implementation. The comment should either be removed or updated to describe the actual safety requirements for this function (e.g., that slot.index must be less than N and the slot must not already be occupied).
| // SAFETY: The caller must uphold the safety rules for the [`RawTableInner::set_ctrl_h2`] | |
| // SAFETY: The caller must ensure that `slot.index` is within bounds of the | |
| // control bytes and `data` arrays, that the slot is currently vacant | |
| // (i.e., does not contain an initialized element), and that the table | |
| // has remaining capacity so that incrementing `self.len` is consistent. |
| @@ -68,96 +54,3 @@ impl<T> SizedTypeProperties for T {} | |||
| pub(crate) struct InsertSlot { | |||
There was a problem hiding this comment.
Grammar error: "an can be inserted" should be "an element can be inserted" or simply "a value can be inserted".
There was a problem hiding this comment.
Code Review
This pull request introduces significant improvements by fixing a logic bug and addressing undefined behavior found by Miri. The fix for the inline insert spill prevents unnecessary heap allocations when updating an existing key in a full inline map, which is a great correctness improvement. The changes to address Miri UB are comprehensive, including adding padding to prevent out-of-bounds SIMD reads, introducing the init-ctrl feature for safe initialization, and refactoring pointer handling to fix aliasing issues. The removal of the Bucket abstraction is a particularly noteworthy improvement, as it greatly simplifies the code, reduces unsafe blocks, and enhances overall soundness. The code quality is excellent, and the addition of a specific test case for the spill bug is appreciated. Overall, this is a very solid and well-executed pull request.
| if tail_len > 0 { | ||
| let group = Group::load(self.aligned_groups.ctrl(probe_pos)); | ||
| let group = Group::load(self.aligned_groups.ctrl_read(probe_pos)); | ||
| for bit in group.match_byte(h2_hash).and(Group::LOWEST_MASK[tail_len]) { |
There was a problem hiding this comment.
to understand it fully, the original code is semantically correct because of the mask, right?
|
maybe add miri to CI? |
No description provided.