Skip to content

Feat/slab allocation - #83

Open
sagi21805 wants to merge 22 commits into
masterfrom
feat/slab-allocation
Open

Feat/slab allocation#83
sagi21805 wants to merge 22 commits into
masterfrom
feat/slab-allocation

Conversation

@sagi21805

@sagi21805 sagi21805 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added slab-based memory allocation with improved allocation tracking and free-space management.
    • Added configurable slab registration and initialization helpers.
    • Integrated slab allocation with page and buddy memory management.
  • Bug Fixes

    • Improved virtual address handling for 48-bit x86 addresses through proper sign extension.
    • Improved transitions between available, partially used, and full allocation areas.
    • Updated 32-bit and 64-bit target configurations for improved build compatibility.

@sagi21805 sagi21805 self-assigned this Aug 2, 2026
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds the slab crate to the workspace, replaces PageMeta with a Page union, introduces state-typed slab descriptors and caches, and integrates them into a generic SlabAllocator.

Changes

Slab allocator redesign

Layer / File(s) Summary
Memory and slab contracts
Cargo.toml, crates/common/src/address_types.rs, crates/memory/page/..., crates/memory/slab/Cargo.toml, crates/memory/slab/src/preallocated.rs, crates/memory/slab/src/traits.rs
The workspace and crate dependencies include the slab crate. Virtual addresses are canonicalized. Page metadata is stored directly in the Page union. Slab traits and preallocated storage define the allocator contracts.
State-typed slab descriptors
crates/memory/slab/src/descriptor.rs
SlabDescriptor<T, S> now represents typed free, partial, full, and used states. Allocation metadata, free-list links, state transitions, and descriptor attachment are implemented.
Slab cache transitions
crates/memory/slab/src/cache.rs
SlabCache uses state-specific descriptor pointers. Allocation moves slabs between partial and full states. Deallocation accepts an index and restores full slabs to partial state.
Generic allocator and slab registration
crates/memory/slab/src/lib.rs, crates/memory/slab/src/local_macros.rs
SlabAllocator<Block, Arena> uses slab caches and buddy arena metadata for allocation and deallocation. New macros register slab types and initialize page-sized caches.
Syntax and target updates
bootloader/..., crates/arch/x86/..., crates/common/..., crates/drivers/vga-display/..., crates/macros/..., crates/sync/..., kernel/..., snippets/...
Const implementation syntax, compact function bodies, target ABI values, and keyboard output formatting were updated.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SlabAllocator
  participant SlabCache
  participant BuddyArena
  participant Page
  participant SlabDescriptor
  SlabAllocator->>SlabCache: kmalloc selects a typed cache
  SlabCache->>SlabDescriptor: allocate an object
  SlabAllocator->>BuddyArena: kfree locks buddy storage
  BuddyArena->>Page: locate the virtual page
  Page->>SlabDescriptor: retrieve the used descriptor
  SlabAllocator->>SlabCache: deallocate by object index
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies slab allocation as the primary change in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/slab-allocation
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/slab-allocation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 20

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/common/src/address_types.rs (1)

166-176: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The range check and the canonical form now disagree.

new accepts only address < (1 << 48). new_unchecked then sign-extends bit 47, so an input in [2^47, 2^48) returns a value at or above 0xFFFF_8000_0000_0000. A caller that passes an already-canonical higher-half address, such as KERNEL_OFFSET, gets None. Accept both encodings, or document that new takes only the low 48 bits.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/common/src/address_types.rs` around lines 166 - 176, The range check
in new must match the canonicalization performed by new_unchecked: accept valid
low-half addresses and already-canonical higher-half addresses such as
KERNEL_OFFSET, while rejecting non-canonical values. Update new’s validation
accordingly, or explicitly document and enforce that it accepts only low 48-bit
inputs; preserve the existing unsafe construction path.
🟡 Minor comments (1)
crates/memory/page/src/lib.rs-20-26 (1)

20-26: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Debug reads an inactive union field and uses a stale name.

fmt always reads self.buddy. If the page holds a slab descriptor, the output is meaningless. The struct name string is still "PageMeta", but the type is now Page. Rename the label. Mark the buddy output as unchecked, or add a discriminant so the formatter can select the active field.

🐛 Proposed fix for the stale name
-        f.debug_struct("PageMeta")
-            .field("buddy", unsafe { &self.buddy })
+        f.debug_struct("Page")
+            // SAFETY: assumes the page is in buddy state.
+            .field("buddy", unsafe { &self.buddy })
             .finish()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/page/src/lib.rs` around lines 20 - 26, Update Page’s Debug
implementation in fmt to use the current struct label "Page" and avoid
unconditionally reading the inactive buddy union field. Either mark the buddy
read as unchecked or, preferably, add/use a discriminant so fmt selects and
reports only the active buddy or slab descriptor field.
🧹 Nitpick comments (5)
crates/memory/slab/src/cache.rs (1)

12-18: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add #[repr(C)] to SlabCache to make the transmutes defensible.

with::<T>() (Line 35) and as_unit() (Line 50) transmute between SlabCache<()> and SlabCache<T>. The default Rust representation gives no layout guarantee between separate monomorphizations. Field order may differ in principle. #[repr(C)] fixes the layout and makes the conversion valid.

♻️ Proposed change
 #[derive(Debug)]
+#[repr(C)]
 pub struct SlabCache<T: Slab> {
     pub buddy_order: usize,
     pub free: Option<NonNull<SlabDescriptor<T, Free>>>,
     pub partial: Option<NonNull<SlabDescriptor<T, Partial>>>,
     pub full: Option<NonNull<SlabDescriptor<T, Full>>>,
 }

The same reasoning applies to SlabDescriptor<T, S>, because the state transitions also transmute between instantiations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/slab/src/cache.rs` around lines 12 - 18, Add #[repr(C)] to both
SlabCache<T> and SlabDescriptor<T, S> so their field layouts remain consistent
across generic instantiations used by with::<T>(), as_unit(), and
state-transition transmutes.
crates/memory/slab/src/local_macros.rs (2)

51-53: 📐 Maintainability & Code Quality | 🔵 Trivial

Tracked TODO.

The comment describes a generated reverse-lookup enum for slab positions. Do you want me to open an issue to track this task?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/slab/src/local_macros.rs` around lines 51 - 53, Replace the
untracked TODO near the reverse-lookup macro logic with an actionable tracking
reference, such as an issue link or identifier, for implementing the generated
enum-based reverse lookup and validating its compiler output. Preserve the
existing intent and book-documentation note.

26-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify COUNT and relax the init receiver.

Line 26 builds a temporary array of string literals only to read its length. A repetition sum states the intent directly and avoids the element type.

init at Line 38 takes &mut self, but the body only reads self.slab_arena, which is a &'static SpinMutex. &self is sufficient and lets a caller initialize the allocator through a shared static. The loop also locks and unlocks SLAB_ARENA once per registered type; a single guard covers the whole loop.

♻️ Proposed change
-        const COUNT: usize = [$(stringify!($t)),*].len();
+        const COUNT: usize = 0 $(+ { let _ = ::core::stringify!($t); 1 })*;
-            pub fn init(&mut self) {
+            pub fn init(&self) {
+                let mut arena = self.slab_arena.lock();
                 $(
                     let index = <$t>::SLAB_POSITION;

                     let slab_cache = SlabCache::<$t>::new(size_of::<$t>().div_ceil(REGULAR_PAGE_SIZE));

-                    self.slab_arena.lock()[index] = unsafe { slab_cache.as_unit() };
+                    arena[index] = unsafe { slab_cache.as_unit() };
                 )*
             }

Also applies to: 38-46

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/slab/src/local_macros.rs` at line 26, Update the macro’s COUNT
calculation to use a repetition sum instead of constructing a temporary
stringify array. In init, change the receiver from &mut self to &self, then
acquire the SLAB_ARENA lock once before iterating over all registered types and
reuse that guard throughout the loop.
crates/memory/slab/Cargo.toml (1)

11-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pin the num_enum git dependency to a revision.

The dependency points at a branch head of a personal fork. Builds are not reproducible, and a force push changes the compiled code. Add rev or tag.

♻️ Proposed change
-num_enum = { git = "https://github.com/sagi21805/num_enum.git", default-features = false, features = [
+num_enum = { git = "https://github.com/sagi21805/num_enum.git", rev = "<commit-sha>", default-features = false, features = [
     "complex-expressions",
 ] }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/slab/Cargo.toml` around lines 11 - 13, Pin the num_enum git
dependency in Cargo.toml to a specific immutable revision or tag by adding the
appropriate rev or tag field. Keep the existing repository URL, default-features
setting, and complex-expressions feature unchanged.
Cargo.toml (1)

20-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Track the disabled tests workspace member.

The tests member is commented out. The workspace no longer builds or runs that crate. Restore it before merge, or record a follow-up task so the tests are re-enabled after the slab refactor lands.

Do you want me to open an issue to track re-enabling tests?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Cargo.toml` at line 20, Restore the commented-out "tests" workspace member in
the Cargo.toml workspace configuration so the crate is included in builds and
test runs; if it cannot be re-enabled during this change, create a follow-up
task tracking its restoration after the slab refactor.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/common/src/address_types.rs`:
- Around line 163-164: Update VirtualAddress::new_unchecked so the bit-47
sign-extension is performed only when target_pointer_width is 64; preserve the
direct address conversion on 32-bit targets, using the existing target
configuration or fixed-width arithmetic to avoid applying 64-bit
canonicalization to usize.

In `@crates/memory/page/src/lib.rs`:
- Around line 39-45: Update the meta_mut and meta methods to borrow self.buddy
directly instead of converting a shared-reference pointer through
NonNull::from_ref. Return a mutable reference from meta_mut and a shared
reference from meta while preserving their existing signatures and behavior.

In `@crates/memory/slab/src/cache.rs`:
- Around line 129-134: Update the Ok(partial) branch around partial.dealloc in
the slab cache deallocation flow to detect when
partial.state.get_total_allocated() reaches zero. Remove the emptied slab
descriptor from the partial list, reinitialize its state as FullFreeMeta, and
push it onto self.free; preserve the existing deallocation behavior for slabs
that still contain live objects.
- Around line 76-98: Normalize descriptor metadata and list heads across all
SlabCache state transitions. In crates/memory/slab/src/cache.rs lines 76-98,
update the partial-to-full path to write a fresh FullFreeMeta, set next to the
target head, update the source and target heads, clear next when the full list
was empty, and handle the normal SlabStateKind::Partial result instead of
asserting unreachable. In lines 100-114, update the free-list head and
unconditionally set partial.next to self.partial during free-to-partial. In
lines 135-147, update self.full when detaching its head, then rewrite the
descriptor state, set next to the partial head, and push it onto self.partial.

In `@crates/memory/slab/src/descriptor.rs`:
- Around line 223-233: Update the unsafe dealloc method to explicitly drop the
ManuallyDrop<T> object in the freed slot before overwriting it with the
free-list link, ensuring owned resources are released while preserving the
existing allocator bookkeeping.
- Around line 148-163: Validate the object count computed before the free-list
loop in the descriptor initialization path, ensuring it is strictly less than
u16::MAX before converting indices to u16. Add an appropriate compile-time or
runtime assertion, then preserve the existing PreAllocated initialization for
valid counts.
- Around line 189-216: Update Descriptor::alloc to cast the
NonNull<ManuallyDrop<T>> derived from preallocated.allocated to NonNull<T>, and
prevent allocation when the slab has no remaining capacity by asserting
total_allocated is below T::OBJECT_PER_SLAB or changing the API to return None
for a full slab; preserve the existing partial/full state updates for valid
allocations.
- Around line 70-82: Update the PartialMeta bitfield definition to total 64
bits, adding the required padding while preserving the existing next_free_idx,
total_allocated, and partial fields. Ensure PartialMeta has the same layout size
as RawMeta and FullFreeMeta so SlabDescriptor<T, S> metadata transmutations
remain valid.
- Around line 275-285: Update Descriptor::detach to copy only the detached
descriptor’s previous-link value into the next descriptor, preserving its other
state fields. After relinking the neighboring descriptors, clear self.next and
self.state’s previous link so repeated detach or later attach operations cannot
follow stale links.
- Around line 116-120: Update SlabDescriptor::from_non_null so failed conversion
of ptr.addr() to NonZeroU64 is not discarded through .ok(). Propagate the
conversion error or otherwise return it to the caller, and adjust the method’s
return type and callers as needed so overflow cannot be interpreted as None or
cause descriptor unlinking.
- Around line 138-146: Update the allocation logic in the descriptor constructor
around `NonNull::new_unchecked` to validate the computed layout before
allocation and handle layout creation failure, including overflow from large
`order` values. Check the pointer returned by `alloc` before converting it to
`NonNull`, returning an error or panicking on either failure instead of invoking
unchecked operations.

In `@crates/memory/slab/src/lib.rs`:
- Around line 80-84: Update kmalloc and kfree to follow a single lock order:
acquire buddy_arena before slab_arena. Ensure kmalloc holds or obtains
buddy_arena before entering the slab_arena allocation path, matching kfree and
preserving the existing allocation behavior while allowing the future
SlabCache::alloc grow path to use the page allocator safely.
- Around line 101-110: Update the index calculation in the kfree path to compute
the offset in PreAllocated<T> slots rather than T-sized elements. Base the
pointer arithmetic on the descriptor’s PreAllocated<T> object storage,
preserving the existing NonMaxU16 validation and unreachable behavior for
objects outside the page.

In `@crates/memory/slab/src/local_macros.rs`:
- Around line 32-35: Add the missing 'static bound to Arena in the SlabAllocator
impl's where clause, alongside BuddyArena<Block>, so it matches the constraint
declared on SlabAllocator and proves the self type is well-formed.
- Around line 22-34: Update the exported macro define_slab_system! to qualify
all referenced types and constants through $crate, including SlabCache,
SpinMutex, SlabAllocator, BuddyBlock, SlabBlock, and BuddyArena, so expansion
does not depend on caller imports. Replace the injected REGULAR_PAGE_SIZE use
with a fully qualified ::common::constants::REGULAR_PAGE_SIZE reference at both
existing locations, avoiding namespace collisions while preserving the macro’s
behavior.
- Line 42: Update the slab_cache initialization in the local macro to convert
the computed page count into the buddy allocation order expected by
SlabCache::new. Use the smallest order whose power-of-two page capacity
accommodates size_of::<$t>().div_ceil(REGULAR_PAGE_SIZE), preserving correct
allocation for single- and multi-page slab types.

In `@crates/memory/slab/src/preallocated.rs`:
- Around line 10-17: The Debug implementation for PreAllocated<T> must not
unconditionally read both union fields; update it to format raw bytes or accept
the known active state, without directly formatting allocated and next_free_idx
together. In crates/memory/slab/src/preallocated.rs lines 10-17, modify Debug
for PreAllocated. In crates/memory/page/src/lib.rs lines 20-26, retain the buddy
read but document it as an assumption in a comment and rename the debug label
from "PageMeta" to "Page".

In `@crates/memory/slab/src/traits.rs`:
- Line 15: Replace the empty `impl Slab for ()` in the `Slab` trait
implementations with an explicit implementation that provides safe non-zero
constants for unit-sized erased storage, or switch the erased cache marker to a
dedicated non-zero-sized type and update the corresponding cache
storage/transmute path. Ensure `OBJECT_PER_SLAB` is never derived by dividing by
`size_of::<()>()`.
- Around line 19-23: Update the `OBJECT_PER_SLAB` constant to calculate capacity
from the slab’s page count multiplied by `REGULAR_PAGE_SIZE`, divided by
`size_of::<PreAllocated<Self>>()`; do not divide `PAGES_PER_SLAB` by
`size_of::<Self>()`. Preserve `PAGES_PER_SLAB` and ensure the resulting count
matches the `PreAllocated<T>` object-array stride used by slab allocation.
- Around line 56-76: Update the SlabBlock trait’s slab_descriptor and
slab_descriptor_mut methods to be unsafe fn declarations, preserving their
existing signatures and # Safety sections. Correct both doc comments by changing
“Retrive” to “Retrieve” and describing the unchecked cast as occurring
“unconditionally, which is unsafe.”

---

Outside diff comments:
In `@crates/common/src/address_types.rs`:
- Around line 166-176: The range check in new must match the canonicalization
performed by new_unchecked: accept valid low-half addresses and
already-canonical higher-half addresses such as KERNEL_OFFSET, while rejecting
non-canonical values. Update new’s validation accordingly, or explicitly
document and enforce that it accepts only low 48-bit inputs; preserve the
existing unsafe construction path.

---

Minor comments:
In `@crates/memory/page/src/lib.rs`:
- Around line 20-26: Update Page’s Debug implementation in fmt to use the
current struct label "Page" and avoid unconditionally reading the inactive buddy
union field. Either mark the buddy read as unchecked or, preferably, add/use a
discriminant so fmt selects and reports only the active buddy or slab descriptor
field.

---

Nitpick comments:
In `@Cargo.toml`:
- Line 20: Restore the commented-out "tests" workspace member in the Cargo.toml
workspace configuration so the crate is included in builds and test runs; if it
cannot be re-enabled during this change, create a follow-up task tracking its
restoration after the slab refactor.

In `@crates/memory/slab/Cargo.toml`:
- Around line 11-13: Pin the num_enum git dependency in Cargo.toml to a specific
immutable revision or tag by adding the appropriate rev or tag field. Keep the
existing repository URL, default-features setting, and complex-expressions
feature unchanged.

In `@crates/memory/slab/src/cache.rs`:
- Around line 12-18: Add #[repr(C)] to both SlabCache<T> and SlabDescriptor<T,
S> so their field layouts remain consistent across generic instantiations used
by with::<T>(), as_unit(), and state-transition transmutes.

In `@crates/memory/slab/src/local_macros.rs`:
- Around line 51-53: Replace the untracked TODO near the reverse-lookup macro
logic with an actionable tracking reference, such as an issue link or
identifier, for implementing the generated enum-based reverse lookup and
validating its compiler output. Preserve the existing intent and
book-documentation note.
- Line 26: Update the macro’s COUNT calculation to use a repetition sum instead
of constructing a temporary stringify array. In init, change the receiver from
&mut self to &self, then acquire the SLAB_ARENA lock once before iterating over
all registered types and reuse that guard throughout the loop.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e6546d52-51c0-4bd6-9ffb-88eaef681f8a

📥 Commits

Reviewing files that changed from the base of the PR and between 81ac8c6 and 16fcaec.

📒 Files selected for processing (15)
  • Cargo.toml
  • crates/common/src/address_types.rs
  • crates/memory/page/Cargo.toml
  • crates/memory/page/src/arena.rs
  • crates/memory/page/src/lib.rs
  • crates/memory/page/src/meta.rs
  • crates/memory/slab/Cargo.toml
  • crates/memory/slab/src/cache.rs
  • crates/memory/slab/src/descriptor.rs
  • crates/memory/slab/src/lib.rs
  • crates/memory/slab/src/local_macros.rs
  • crates/memory/slab/src/macros.rs
  • crates/memory/slab/src/preallocated.rs
  • crates/memory/slab/src/traits.rs
  • crates/memory/slab/src/unassigned.rs
💤 Files with no reviewable changes (3)
  • crates/memory/slab/src/unassigned.rs
  • crates/memory/page/src/meta.rs
  • crates/memory/slab/src/macros.rs

Comment on lines +163 to 164
Self(((address << 16) as isize >> 16) as usize)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find target specifications and per-crate target settings.
fd -e json -e toml --hidden | rg -n 'target|x86' | head -50
fd '^\.cargo$' -t d --hidden --exec cat {}/config.toml
rg -n 'target_arch\s*=\s*"x86"' --type rust -C2
rg -n 'common' -g 'Cargo.toml' -C2 bootloader

Repository: sagi21805/LearnixOS

Length of output: 439


🏁 Script executed:

#!/bin/bash
set -e

# Locate and inspect the relevant Rust source and Cargo metadata around common/arch targets.
printf 'Tracked Rust/config files:\n'
git ls-files | rg '(^crates/|Cargo\.toml$|\.cargo/config|32bit_target|64bit_target|16bit_target)' || true

printf '\naddress_types.rs outline:\n'
ast-grep outline crates/common/src/address_types.rs || true

printf '\naddress_types.rs relevant lines:\n'
cat -n crates/common/src/address_types.rs | sed -n '1,240p'

printf '\nTarget json files:\n'
cat -n bootloader/second_stage/32bit_target.json
printf '\n---\n'
cat -n kernel/64bit_target.json

printf '\nCommon references in Cargo.toml files:\n'
rg -n 'common|crates/common|crate_name|target =' -g 'Cargo.toml' -C2 .

printf '\nArchitecture target checks in Rust:\n'
rg -n 'target_arch\s*=\s*"x86"|target_arch\s*=\s*"x86_64"|target_pointer_width\s*=\s*"|new_unchecked|canonicalize' --type rust -C2 .

Repository: sagi21805/LearnixOS

Length of output: 50375


Gate virtual-address canonicalization to 64-bit pointer width.

VirtualAddress::new_unchecked signs-extends bit 47 after shifting by 16, but usize is 32 bits on the target_arch = "x86" build (p:32:32). That canonicalization no longer applies only to the 64-bit path; apply it only when target_pointer_width = "64" or use fixed-width u64/i64 arithmetic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/common/src/address_types.rs` around lines 163 - 164, Update
VirtualAddress::new_unchecked so the bit-47 sign-extension is performed only
when target_pointer_width is 64; preserve the direct address conversion on
32-bit targets, using the existing target configuration or fixed-width
arithmetic to avoid applying 64-bit canonicalization to usize.

Comment on lines 39 to 45
fn meta_mut(&mut self) -> &mut BuddyMeta<Regular> {
unsafe { NonNull::from_ref(&self.meta.buddy).as_mut() }
unsafe { NonNull::from_ref(&self.buddy).as_mut() }
}

fn meta(&self) -> &BuddyMeta<Regular> {
unsafe { NonNull::from_ref(&self.meta.buddy).as_ref() }
unsafe { NonNull::from_ref(&self.buddy).as_ref() }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not create a mutable reference from a shared-reference pointer.

meta_mut calls NonNull::from_ref(&self.buddy) and then as_mut(). from_ref takes a shared reference, so the resulting pointer carries read-only provenance. Writing through it is undefined behavior under Stacked Borrows. Borrow the field directly instead. meta also does not need the pointer round trip.

🐛 Proposed fix
     fn meta_mut(&mut self) -> &mut BuddyMeta<Regular> {
-        unsafe { NonNull::from_ref(&self.buddy).as_mut() }
+        unsafe { &mut self.buddy }
     }

     fn meta(&self) -> &BuddyMeta<Regular> {
-        unsafe { NonNull::from_ref(&self.buddy).as_ref() }
+        unsafe { &self.buddy }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn meta_mut(&mut self) -> &mut BuddyMeta<Regular> {
unsafe { NonNull::from_ref(&self.meta.buddy).as_mut() }
unsafe { NonNull::from_ref(&self.buddy).as_mut() }
}
fn meta(&self) -> &BuddyMeta<Regular> {
unsafe { NonNull::from_ref(&self.meta.buddy).as_ref() }
unsafe { NonNull::from_ref(&self.buddy).as_ref() }
}
fn meta_mut(&mut self) -> &mut BuddyMeta<Regular> {
unsafe { &mut self.buddy }
}
fn meta(&self) -> &BuddyMeta<Regular> {
unsafe { &self.buddy }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/page/src/lib.rs` around lines 39 - 45, Update the meta_mut and
meta methods to borrow self.buddy directly instead of converting a
shared-reference pointer through NonNull::from_ref. Return a mutable reference
from meta_mut and a shared reference from meta while preserving their existing
signatures and behavior.

Comment on lines +76 to 98
if let Some(partial) =
self.partial.map(|mut p| unsafe { p.as_mut() })
{
let (allocation, final_state) = partial.alloc();
match final_state {
SlabStateKind::Full => {
self.partial = partial.next;
match self.full {
Some(mut full) => unsafe {
full.as_mut()
.attach(core::mem::transmute(partial))
},
None => {
self.full = Some(unsafe {
core::mem::transmute(partial)
});
}
}
}
_ => debug_assert!(false, "unreachable!"),
}
return allocation;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Every state transition in SlabCache leaves the list linkage inconsistent. All three transitions transmute a SlabDescriptor from one state to another, but none of them fully reinitializes the state word or the next field, and none of them updates the cache head that the descriptor is leaving. PartialMeta and FullFreeMeta also have different bit layouts, so a transmute without a state rewrite carries stale bits into the new interpretation. Establish one rule: on every transition, write a fresh state, set next to the target list head, and update both the source head and the target head in SlabCache.

  • crates/memory/slab/src/cache.rs#L76-L98: on the partial-to-full transition, write a fresh FullFreeMeta, clear next in the None arm, and replace the wildcard debug_assert!(false) that fires on the normal SlabStateKind::Partial result.
  • crates/memory/slab/src/cache.rs#L100-L114: on the free-to-partial transition, assign partial.next = self.partial; unconditionally so the descriptor stops pointing into the free list.
  • crates/memory/slab/src/cache.rs#L135-L147: on the full-to-partial transition, update self.full when the detached descriptor is the head, then set next and push the descriptor onto self.partial.
📍 Affects 1 file
  • crates/memory/slab/src/cache.rs#L76-L98 (this comment)
  • crates/memory/slab/src/cache.rs#L100-L114
  • crates/memory/slab/src/cache.rs#L135-L147
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/slab/src/cache.rs` around lines 76 - 98, Normalize descriptor
metadata and list heads across all SlabCache state transitions. In
crates/memory/slab/src/cache.rs lines 76-98, update the partial-to-full path to
write a fresh FullFreeMeta, set next to the target head, update the source and
target heads, clear next when the full list was empty, and handle the normal
SlabStateKind::Partial result instead of asserting unreachable. In lines
100-114, update the free-list head and unconditionally set partial.next to
self.partial during free-to-partial. In lines 135-147, update self.full when
detaching its head, then rewrite the descriptor state, set next to the partial
head, and push it onto self.partial.

Comment thread crates/memory/slab/src/cache.rs Outdated
Comment on lines +129 to +134
match slab.is_partial_mut() {
// TODO: understand how to extract that logic into a function
// on the slab.
Ok(partial) => {
unsafe { partial.dealloc(idx) };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

An emptied partial slab is never moved back to the free list.

The Ok(partial) arm frees the index and returns. When total_allocated reaches 0, the slab holds no live objects, but it stays on the partial list. The cache therefore never returns whole slabs to the page allocator, and grow() keeps adding new slabs. Memory use grows without bound under an allocate-free cycle.

Check partial.state.get_total_allocated() after the dealloc call. If it is 0, unlink the descriptor from the partial list, reinitialize its state as FullFreeMeta, and push it onto self.free.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/slab/src/cache.rs` around lines 129 - 134, Update the
Ok(partial) branch around partial.dealloc in the slab cache deallocation flow to
detect when partial.state.get_total_allocated() reaches zero. Remove the emptied
slab descriptor from the partial list, reinitialize its state as FullFreeMeta,
and push it onto self.free; preserve the existing deallocation behavior for
slabs that still contain live objects.

Comment on lines +70 to +82
#[bitfields]
pub struct PartialMeta {
pub next_free_idx: B16,
pub total_allocated: B31,
pub partial: B1,
}

#[bitfields]
pub struct RawMeta {
#[flag(r)]
pub reserved: B63,
pub partial: B1,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the bitfields macro to determine the generated backing width.
fd -e rs . crates/macros/src --exec rg -n -l 'bitfields' {} \; | head
rg -n 'bitfields' -A40 crates/macros/src | head -120

Repository: sagi21805/LearnixOS

Length of output: 7780


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate relevant files"
fd -e rs 'descriptor.rs|cache.rs|utils.rs|bitfield.rs|bitfields.rs|lib.rs' crates | sed -n '1,120p'

echo
echo "descriptor.rs outline/size"
wc -l crates/memory/slab/src/descriptor.rs
sed -n '1,140p' crates/memory/slab/src/descriptor.rs
echo "--- transmutations/usages ---"
rg -n "transmute|PartialMeta|RawMeta|FullFreeMeta|SlabDescriptor" crates/memory/slab/src/descriptor.rs

echo
echo "cache.rs relevant transmutations"
wc -l crates/memory/slab/src/cache.rs
sed -n '1,180p' crates/memory/slab/src/cache.rs

echo
echo "bitfield macro implementation"
wc -l crates/macros/src/bitfields/bitfield.rs
sed -n '1,240p' crates/macros/src/bitfields/bitfield.rs
wc -l crates/macros/src/bitfields/utils.rs
sed -n '1,260p' crates/macros/src/bitfields/utils.rs
wc -l crates/macros/src/bitfields.rs
sed -n '1,220p' crates/macros/src/bitfields.rs

echo
echo "derive expansion probe: compile-free AST inspection if macro source is available"
python3 - <<'PY'
from pathlib import Path
p=Path('crates/macros/src/bitfields.rs')
print(p.read_text())
PY

Repository: sagi21805/LearnixOS

Length of output: 32159


Make PartialMeta the same size as the other state metadata.

#[bitfields] derives an exact-width wrapper for each struct. PartialMeta sums to 48 bits, but RawMeta and FullFreeMeta are 64 bits, and SlabDescriptor<T, S> is transmuted between these S::Meta variants. Pad PartialMeta to 64 bits and ensure all metadata states share one layout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/slab/src/descriptor.rs` around lines 70 - 82, Update the
PartialMeta bitfield definition to total 64 bits, adding the required padding
while preserving the existing next_free_idx, total_allocated, and partial
fields. Ensure PartialMeta has the same layout size as RawMeta and FullFreeMeta
so SlabDescriptor<T, S> metadata transmutations remain valid.

$(
let index = <$t>::SLAB_POSITION;

let slab_cache = SlabCache::<$t>::new(size_of::<$t>().div_ceil(REGULAR_PAGE_SIZE));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

buddy_order receives a page count, not an order.

size_of::<$t>().div_ceil(REGULAR_PAGE_SIZE) produces a page count. SlabCache::new stores that value in buddy_order (cache.rs Lines 41-48), and SlabDescriptor::new consumes it as an exponent: REGULAR_PAGE_SIZE * (1 << order) (descriptor.rs Lines 134-178).

A slab type that needs 2 pages therefore reports order 2 and allocates 4 pages. The overshoot grows exponentially with the type size.

Convert the page count to an order.

🐛 Proposed fix
-                    let slab_cache = SlabCache::<$t>::new(size_of::<$t>().div_ceil(REGULAR_PAGE_SIZE));
+                    let pages = ::core::mem::size_of::<$t>()
+                        .div_ceil(::common::constants::REGULAR_PAGE_SIZE)
+                        .next_power_of_two();
+                    let slab_cache =
+                        SlabCache::<$t>::new(pages.trailing_zeros() as usize);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let slab_cache = SlabCache::<$t>::new(size_of::<$t>().div_ceil(REGULAR_PAGE_SIZE));
let pages = ::core::mem::size_of::<$t>()
.div_ceil(::common::constants::REGULAR_PAGE_SIZE)
.next_power_of_two();
let slab_cache =
SlabCache::<$t>::new(pages.trailing_zeros() as usize);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/slab/src/local_macros.rs` at line 42, Update the slab_cache
initialization in the local macro to convert the computed page count into the
buddy allocation order expected by SlabCache::new. Use the smallest order whose
power-of-two page capacity accommodates
size_of::<$t>().div_ceil(REGULAR_PAGE_SIZE), preserving correct allocation for
single- and multi-page slab types.

Comment on lines +10 to +17
impl<T: Debug> Debug for PreAllocated<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PreAllocated")
.field("allocated", unsafe { &self.allocated })
.field("next_free_idx", unsafe { &self.next_free_idx })
.finish()
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Both new Debug implementations read union fields without knowing the active variant. A union stores one field at a time. Each fmt here reads a field unconditionally, so the output can interpret unrelated bytes. For a generic T with validity invariants, that read is undefined behavior.

  • crates/memory/slab/src/preallocated.rs#L10-L17: stop formatting allocated and next_free_idx in the same call. Print raw bytes, or take the known state as an argument.
  • crates/memory/page/src/lib.rs#L20-L26: mark the buddy read as an assumption in a comment, and rename the label from "PageMeta" to "Page".
📍 Affects 2 files
  • crates/memory/slab/src/preallocated.rs#L10-L17 (this comment)
  • crates/memory/page/src/lib.rs#L20-L26
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/slab/src/preallocated.rs` around lines 10 - 17, The Debug
implementation for PreAllocated<T> must not unconditionally read both union
fields; update it to format raw bytes or accept the known active state, without
directly formatting allocated and next_free_idx together. In
crates/memory/slab/src/preallocated.rs lines 10-17, modify Debug for
PreAllocated. In crates/memory/page/src/lib.rs lines 20-26, retain the buddy
read but document it as an assumption in a comment and rename the debug label
from "PageMeta" to "Page".

pub trait Slab: 'static + Sized + SlabPosition + SlabFlags {}
pub trait Slab: SlabPosition + SlabFlags {}

impl Slab for () {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

impl Slab for () makes the derived constants divide by zero.

size_of::<()>() is 0. OBJECT_PER_SLAB divides by size_of::<Self>(), so the constant fails const evaluation if anything instantiates it for (). crates/memory/slab/src/lib.rs stores [SlabCache<()>; COUNT] and transmutes it to typed caches, so the unit implementation is reachable. Give () an explicit implementation with safe constants, or use a dedicated erased marker type with a non-zero size.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/slab/src/traits.rs` at line 15, Replace the empty `impl Slab
for ()` in the `Slab` trait implementations with an explicit implementation that
provides safe non-zero constants for unit-sized erased storage, or switch the
erased cache marker to a dedicated non-zero-sized type and update the
corresponding cache storage/transmute path. Ensure `OBJECT_PER_SLAB` is never
derived by dividing by `size_of::<()>()`.

Comment on lines +19 to +23
const PAGES_PER_SLAB: usize = size_of::<Self>()
.next_multiple_of(REGULAR_PAGE_SIZE)
/ REGULAR_PAGE_SIZE;
const OBJECT_PER_SLAB: usize =
Self::PAGES_PER_SLAB / size_of::<Self>();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

OBJECT_PER_SLAB computes the wrong value.

PAGES_PER_SLAB is a page count, not a byte count. Dividing a page count by size_of::<Self>() gives 0 for every type larger than one byte. Example: for a 64-byte type, PAGES_PER_SLAB is 1 and OBJECT_PER_SLAB is 0.

crates/memory/slab/src/cache.rs at Line 145 uses this constant as the full-slab allocation count: .total_allocated(T::OBJECT_PER_SLAB as u32). The following partial.dealloc(idx) decrements total_allocated, so the count underflows on the first free of a full slab.

Multiply by the page size first. The slab stride is size_of::<PreAllocated<T>>(), not size_of::<Self>(), because crates/memory/slab/src/descriptor.rs at Line 151 sizes the object array with PreAllocated<T>.

🐛 Proposed fix
-    const OBJECT_PER_SLAB: usize =
-        Self::PAGES_PER_SLAB / size_of::<Self>();
+    const OBJECT_PER_SLAB: usize = (Self::PAGES_PER_SLAB
+        * REGULAR_PAGE_SIZE)
+        / size_of::<PreAllocated<Self>>();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const PAGES_PER_SLAB: usize = size_of::<Self>()
.next_multiple_of(REGULAR_PAGE_SIZE)
/ REGULAR_PAGE_SIZE;
const OBJECT_PER_SLAB: usize =
Self::PAGES_PER_SLAB / size_of::<Self>();
const PAGES_PER_SLAB: usize = size_of::<Self>()
.next_multiple_of(REGULAR_PAGE_SIZE)
/ REGULAR_PAGE_SIZE;
const OBJECT_PER_SLAB: usize = (Self::PAGES_PER_SLAB
* REGULAR_PAGE_SIZE)
/ size_of::<PreAllocated<Self>>();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/slab/src/traits.rs` around lines 19 - 23, Update the
`OBJECT_PER_SLAB` constant to calculate capacity from the slab’s page count
multiplied by `REGULAR_PAGE_SIZE`, divided by `size_of::<PreAllocated<Self>>()`;
do not divide `PAGES_PER_SLAB` by `size_of::<Self>()`. Preserve `PAGES_PER_SLAB`
and ensure the resulting count matches the `PreAllocated<T>` object-array stride
used by slab allocation.

Comment on lines +56 to +76
pub trait SlabBlock {
fn from_address(address: VirtualAddress) -> Self;

/// Retrive a reference to the [`SlabDescriptor`] from the block.
///
/// # Safety
///
/// The `T` that this function gets will cast the [`SlabDescriptor`]
/// that in this block unconditionality which in unsafe.
fn slab_descriptor<T: Slab>(&self) -> &SlabDescriptor<T, Used>;

/// Retrive a mutable reference [`SlabDescriptor`] from the block.
///
/// # Safety
///
/// The `T` that this function gets will cast the [`SlabDescriptor`]
/// that in this block unconditionality which in unsafe.
fn slab_descriptor_mut<T: Slab>(
&mut self,
) -> &mut SlabDescriptor<T, Used>;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Mark the descriptor accessors unsafe, and fix the doc text.

The doc comments state that the T cast is unchecked and unsafe, but both functions are safe. A caller can pick any T and obtain a mismatched SlabDescriptor<T, Used> without an unsafe block. Declare both functions unsafe fn, and keep the # Safety sections.

The doc text also contains typos: "Retrive" → "Retrieve", "unconditionality which in unsafe" → "unconditionally, which is unsafe".

♻️ Proposed change
-    /// Retrive a reference to the [`SlabDescriptor`] from the block.
+    /// Retrieve a reference to the [`SlabDescriptor`] from the block.
     ///
     /// # Safety
     ///
-    /// The `T` that this function gets will cast the [`SlabDescriptor`]
-    /// that in this block unconditionality which in unsafe.
-    fn slab_descriptor<T: Slab>(&self) -> &SlabDescriptor<T, Used>;
+    /// The caller must guarantee that this block holds a slab
+    /// descriptor for `T`. The cast is unconditional.
+    unsafe fn slab_descriptor<T: Slab>(&self) -> &SlabDescriptor<T, Used>;
 
-    /// Retrive a mutable reference [`SlabDescriptor`] from the block.
+    /// Retrieve a mutable reference to the [`SlabDescriptor`] from the
+    /// block.
     ///
     /// # Safety
     ///
-    /// The `T` that this function gets will cast the [`SlabDescriptor`]
-    /// that in this block unconditionality which in unsafe.
-    fn slab_descriptor_mut<T: Slab>(
+    /// The caller must guarantee that this block holds a slab
+    /// descriptor for `T`. The cast is unconditional.
+    unsafe fn slab_descriptor_mut<T: Slab>(
         &mut self,
     ) -> &mut SlabDescriptor<T, Used>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub trait SlabBlock {
fn from_address(address: VirtualAddress) -> Self;
/// Retrive a reference to the [`SlabDescriptor`] from the block.
///
/// # Safety
///
/// The `T` that this function gets will cast the [`SlabDescriptor`]
/// that in this block unconditionality which in unsafe.
fn slab_descriptor<T: Slab>(&self) -> &SlabDescriptor<T, Used>;
/// Retrive a mutable reference [`SlabDescriptor`] from the block.
///
/// # Safety
///
/// The `T` that this function gets will cast the [`SlabDescriptor`]
/// that in this block unconditionality which in unsafe.
fn slab_descriptor_mut<T: Slab>(
&mut self,
) -> &mut SlabDescriptor<T, Used>;
}
pub trait SlabBlock {
fn from_address(address: VirtualAddress) -> Self;
/// Retrieve a reference to the [`SlabDescriptor`] from the block.
///
/// # Safety
///
/// The caller must guarantee that this block holds a slab
/// descriptor for `T`. The cast is unconditional.
unsafe fn slab_descriptor<T: Slab>(&self) -> &SlabDescriptor<T, Used>;
/// Retrieve a mutable reference to the [`SlabDescriptor`] from the
/// block.
///
/// # Safety
///
/// The caller must guarantee that this block holds a slab
/// descriptor for `T`. The cast is unconditional.
unsafe fn slab_descriptor_mut<T: Slab>(
&mut self,
) -> &mut SlabDescriptor<T, Used>;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/slab/src/traits.rs` around lines 56 - 76, Update the SlabBlock
trait’s slab_descriptor and slab_descriptor_mut methods to be unsafe fn
declarations, preserving their existing signatures and # Safety sections.
Correct both doc comments by changing “Retrive” to “Retrieve” and describing the
unchecked cast as occurring “unconditionally, which is unsafe.”

@sagi21805

Copy link
Copy Markdown
Owner Author

Handle case where freeing an object from partial descriptor results in an empty one.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/memory/slab/src/cache.rs`:
- Around line 50-59: Update find_partial so it checks each current descriptor,
including the tail, against partial and returns the matching current descriptor
rather than next. Preserve None when no descriptor matches.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 857f5ce6-5339-47cd-9744-02d32509aa6c

📥 Commits

Reviewing files that changed from the base of the PR and between 16fcaec and 05c4032.

📒 Files selected for processing (2)
  • crates/memory/slab/src/cache.rs
  • crates/memory/slab/src/descriptor.rs

Comment on lines +50 to +59
pub fn find_partial(&self, partial: &SlabDescriptor<T, Partial>) -> Option<&SlabDescriptor<T, Partial>> {
let mut current = self.partial?;
while let Some(next) = unsafe { current.as_ref().next } {
if NonNull::from_ref(partial) == current {
return Some(unsafe { next.as_ref() });
}
current = next;
}
None
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Return the matched partial descriptor.

Line 53 compares current with partial, but Line 54 returns next. The loop also skips the tail descriptor. A caller can therefore reclassify or unlink a different descriptor.

Proposed fix
 pub fn find_partial(&self, partial: &SlabDescriptor<T, Partial>) -> Option<&SlabDescriptor<T, Partial>> {
     let mut current = self.partial?;
-    while let Some(next) = unsafe { current.as_ref().next } {
+    loop {
         if NonNull::from_ref(partial) == current  {
-            return Some(unsafe { next.as_ref() });
+            return Some(unsafe { current.as_ref() });
         }
-        current = next;
+        current = unsafe { current.as_ref().next? };
     }
-    None
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn find_partial(&self, partial: &SlabDescriptor<T, Partial>) -> Option<&SlabDescriptor<T, Partial>> {
let mut current = self.partial?;
while let Some(next) = unsafe { current.as_ref().next } {
if NonNull::from_ref(partial) == current {
return Some(unsafe { next.as_ref() });
}
current = next;
}
None
}
pub fn find_partial(&self, partial: &SlabDescriptor<T, Partial>) -> Option<&SlabDescriptor<T, Partial>> {
let mut current = self.partial?;
loop {
if NonNull::from_ref(partial) == current {
return Some(unsafe { current.as_ref() });
}
current = unsafe { current.as_ref().next? };
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/slab/src/cache.rs` around lines 50 - 59, Update find_partial so
it checks each current descriptor, including the tail, against partial and
returns the matching current descriptor rather than next. Preserve None when no
descriptor matches.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
crates/sync/src/spsc.rs (2)

79-79: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the safety contracts for these public unsafe methods.

buffer, Producer::inner, and Consumer::inner have no # Safety sections. Document the aliasing and lifetime requirements for callers. Make inner safe if it has no caller-side safety precondition.

Also applies to: 113-113, 146-146

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sync/src/spsc.rs` at line 79, Document the caller safety contracts for
the public unsafe methods buffer, Producer::inner, and Consumer::inner,
including their aliasing and lifetime requirements. Review whether each inner
method has any caller-side safety precondition; make it safe and remove
unnecessary unsafe requirements if not, while preserving unsafe only where
required.

Source: Linters/SAST tools


113-113: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Return the stored reference directly.

self.buf already has type &SpscRingBuffer<T>. Returning &self.buf creates an extra reference and relies on dereference coercion. Return self.buf directly in both methods.

Also applies to: 146-146

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sync/src/spsc.rs` at line 113, Update both `inner` methods in
`SpscRingBuffer` to return the stored `self.buf` reference directly instead of
borrowing it again as `&self.buf`, preserving their existing return types and
unsafe behavior.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/memory/page/src/lib.rs`:
- Around line 13-17: Update the Page union’s slab field to use
SlabDescriptor<(), Used> wrapped in core::mem::ManuallyDrop, and import or
qualify ManuallyDrop and Used as needed. Preserve the existing
BuddyMeta<Regular> field and ensure the descriptor matches the state required by
slab_descriptor and slab_descriptor_mut.

In `@crates/sync/src/spsc.rs`:
- Around line 82-83: Implement Drop for SpscRingBuffer<T> by reclaiming the
backing allocation created by new instead of calling todo!(); ensure the owned
Box<[T]> is retained or reconstructed and released exactly once when the buffer
is dropped, without panicking.

---

Nitpick comments:
In `@crates/sync/src/spsc.rs`:
- Line 79: Document the caller safety contracts for the public unsafe methods
buffer, Producer::inner, and Consumer::inner, including their aliasing and
lifetime requirements. Review whether each inner method has any caller-side
safety precondition; make it safe and remove unnecessary unsafe requirements if
not, while preserving unsafe only where required.
- Line 113: Update both `inner` methods in `SpscRingBuffer` to return the stored
`self.buf` reference directly instead of borrowing it again as `&self.buf`,
preserving their existing return types and unsafe behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d11db19-6001-4871-810d-8629e8c1917a

📥 Commits

Reviewing files that changed from the base of the PR and between 05c4032 and 56d5ac7.

📒 Files selected for processing (21)
  • bootloader/first_stage/src/main.rs
  • bootloader/second_stage/32bit_target.json
  • bootloader/second_stage/src/main.rs
  • crates/arch/x86/src/memory_map.rs
  • crates/arch/x86/src/structures/global_descriptor_table.rs
  • crates/common/src/address_types.rs
  • crates/common/src/alloc.rs
  • crates/common/src/enums/keyboard.rs
  • crates/common/src/enums/paging.rs
  • crates/common/src/late_init.rs
  • crates/drivers/vga-display/src/color_code.rs
  • crates/drivers/vga-display/src/screen_char.rs
  • crates/drivers/vga-display/src/writer.rs
  • crates/macros/src/bitfields.rs
  • crates/memory/page/src/lib.rs
  • crates/memory/slab/src/cache.rs
  • crates/sync/src/spsc.rs
  • kernel/64bit_target.json
  • kernel/src/main.rs
  • snippets/src/book/ch02_02/flag_macro_expand.rs
  • snippets/src/book/flag_macro_expand.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/common/src/address_types.rs
  • crates/memory/slab/src/cache.rs

Comment on lines +13 to +17
use slab::descriptor::SlabDescriptor;

pub union Page {
pub buddy: BuddyMeta<Regular>,
pub slab: SlabDescriptor<()>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 \
  'pub struct SlabDescriptor|pub slab:|ManuallyDrop|SlabDescriptor<.*(Used|Free|Partial|Full)' \
  crates/memory/page/src/lib.rs \
  crates/memory/slab/src/descriptor.rs \
  crates/memory/slab/src/cache.rs \
  crates/memory/slab/src/traits.rs

Repository: sagi21805/LearnixOS

Length of output: 8894


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- page lib ---\n'
cat -n crates/memory/page/src/lib.rs

printf '\n--- descriptor state/impls ---\n'
sed -n '10,90p' crates/memory/slab/src/descriptor.rs | cat -n
sed -n '1,50p' crates/memory/slab/src/traits.rs | cat -n

printf '\n--- slab descriptor field declarations/usages ---\n'
rg -n 'pub slab:|\\.slab|slab_descriptor|SlabDescriptor<' crates/memory -g '*.rs'

Repository: sagi21805/LearnixOS

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- page lib ---'
cat -n crates/memory/page/src/lib.rs

echo
echo '--- descriptor state/impls ---'
sed -n '1,90p' crates/memory/slab/src/traits.rs | cat -n
sed -n '10,90p' crates/memory/slab/src/descriptor.rs | cat -n

echo
echo '--- slab descriptor field declarations/usages ---'
rg -n 'pub slab:|\.slab|slab_descriptor|SlabDescriptor<' crates/memory -g '*.rs'

Repository: sagi21805/LearnixOS

Length of output: 11941


🌐 Web query:

Rust union field types Copy or ManuallyDrop non-copy restrictions

💡 Result:

In Rust, union field types are restricted to prevent the compiler from implicitly running "drop glue" (the code that executes when a variable is dropped) [1][2]. Because unions cannot track which field is currently active, the compiler cannot safely determine which destructor to run, and thus it forbids fields that might require one [2]. To be valid as a union field, a type must satisfy one of the following conditions: 1. It must implement the Copy trait [1][3]. 2. It must be wrapped in std::mem::ManuallyDrop [1][3]. If a type does not implement Copy, it cannot be used directly as a union field [4][5]. Wrapping the type in ManuallyDrop tells the compiler that you are taking manual responsibility for the memory management of that field, effectively opting out of automatic drop behavior for that specific member [2][6]. Common pitfalls and notes: - Generic types: When using #[derive(Copy)] on a struct with a generic parameter T, the compiler implicitly adds a T: Copy bound [4][5]. If your struct's logic does not strictly require T to be Copy (e.g., it only holds a reference), the derive macro may cause a compilation error when you try to use that struct in a union [4]. In such cases, you may need to implement Copy manually to avoid the excessive bound [4][5]. - ManuallyDrop usage: While ManuallyDrop allows non-Copy types, it does not magically handle destructors for you [2][6]. You become responsible for ensuring that the contained value is properly dropped (if necessary) by using unsafe operations like std::mem::ManuallyDrop::drop [6]. - Union Drop: Although individual fields are restricted, you can still implement the Drop trait for the union itself if you need to perform custom cleanup logic [1][3]. This restriction is enforced by the compiler to ensure memory safety, as unions that implicitly dropped their fields would lead to undefined behavior [2][3].

Citations:


Use SlabDescriptor<(), Used> inside ManuallyDrop.

SlabDescriptor<T, S> needs two generic arguments, and this field omits the state. The Used state is required for the slab contract in slab_descriptor[T] and slab_descriptor_mut[T]. Rust unions also forbid non-Copy fields, so wrap the descriptor in core::mem::ManuallyDrop.

🐛 Proposed fix
-use slab::descriptor::SlabDescriptor;
+use slab::descriptor::{SlabDescriptor, Used};

 pub union Page {
     pub buddy: BuddyMeta<Regular>,
-    pub slab: SlabDescriptor<()>,
+    pub slab: ManuallyDrop<SlabDescriptor<(), Used>>,
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/memory/page/src/lib.rs` around lines 13 - 17, Update the Page union’s
slab field to use SlabDescriptor<(), Used> wrapped in core::mem::ManuallyDrop,
and import or qualify ManuallyDrop and Used as needed. Preserve the existing
BuddyMeta<Regular> field and ensure the descriptor matches the state required by
slab_descriptor and slab_descriptor_mut.

Source: MCP tools

Comment thread crates/sync/src/spsc.rs
Comment on lines 82 to +83
impl<T: Clone + Copy> Drop for SpscRingBuffer<T> {
fn drop(&mut self) {
todo!()
}
fn drop(&mut self) { todo!() }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Implement Drop before exposing this type.

Drop::drop always calls todo!(). Any non-static SpscRingBuffer created by new panics when it goes out of scope. new also leaks the backing Box<[T]>.

Reclaim the allocation in drop, or store the Box<[T]> directly.

Proposed fix
 impl<T: Clone + Copy> Drop for SpscRingBuffer<T> {
-    fn drop(&mut self) { todo!() }
+    fn drop(&mut self) {
+        unsafe {
+            drop(Box::from_raw(self.buffer.as_ptr()));
+        }
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sync/src/spsc.rs` around lines 82 - 83, Implement Drop for
SpscRingBuffer<T> by reclaiming the backing allocation created by new instead of
calling todo!(); ensure the owned Box<[T]> is retained or reconstructed and
released exactly once when the buffer is dropped, without panicking.

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.

1 participant