-
Notifications
You must be signed in to change notification settings - Fork 121
Compacting GC: thread backwards pointers in marking #2647
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
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
956491f
Copy tests from compacting_gc_4_rebase
osa1 8ad139c
Copy mark stack changes from compacting_gc_4_rebase
osa1 b7ce344
Initial implementation, does not work yet
osa1 f7297a7
Fix a bug in mark_root_mutbox_fields
osa1 953a820
Fix a bug in update_refs
osa1 59acd96
Fix a bug in mark_root_mutbox_fields
osa1 ac69106
Clarify push_mark_stack tag argument in tests
osa1 6386ba8
Update module documentation
osa1 d9f5d24
Merge remote-tracking branch 'origin/master' into osa1/compacting_gc_5
osa1 2fe52d8
Merge remote-tracking branch 'origin/master' into osa1/compacting_gc_5
osa1 0c21d69
Compare pointers with object address instead of field
osa1 edd2fc1
Merge remote-tracking branch 'origin/master' into osa1/compacting_gc_5
osa1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| //! Implements "threaded compaction" as described in The Garbage Collection Handbook section 3.3. | ||
| //! Implements threaded compaction as described in "High-Performance Garbage Collection for | ||
| //! Memory-Constrained Environments" section 5.1.2, which is an improved version of the original | ||
| //! threaded compaction algorithm described in The Garbage Collection Handbook section 3.3. | ||
|
|
||
| pub mod bitmap; | ||
| pub mod mark_stack; | ||
|
|
@@ -10,7 +12,7 @@ use crate::constants::WORD_SIZE; | |
| use crate::mem_utils::memcpy_words; | ||
| use crate::memory::Memory; | ||
| use crate::types::*; | ||
| use crate::visitor::visit_pointer_fields; | ||
| use crate::visitor::{pointer_to_dynamic_heap, visit_pointer_fields}; | ||
|
|
||
| use motoko_rts_macros::ic_mem_fn; | ||
|
|
||
|
|
@@ -46,7 +48,7 @@ pub unsafe fn compacting_gc_internal< | |
| get_hp: GetHp, | ||
| set_hp: SetHp, | ||
| static_roots: SkewedPtr, | ||
| closure_table_loc: *mut SkewedPtr, | ||
| closure_table_ptr_loc: *mut SkewedPtr, | ||
| note_live_size: NoteLiveSize, | ||
| note_reclaimed: NoteReclaimed, | ||
| ) { | ||
|
|
@@ -58,7 +60,7 @@ pub unsafe fn compacting_gc_internal< | |
| heap_base, | ||
| old_hp, | ||
| static_roots, | ||
| closure_table_loc, | ||
| closure_table_ptr_loc, | ||
| ); | ||
|
|
||
| let reclaimed = old_hp - (get_hp() as u32); | ||
|
|
@@ -74,7 +76,7 @@ unsafe fn mark_compact<M: Memory, SetHp: Fn(u32)>( | |
| heap_base: u32, | ||
| heap_end: u32, | ||
| static_roots: SkewedPtr, | ||
| closure_table_loc: *mut SkewedPtr, | ||
| closure_table_ptr_loc: *mut SkewedPtr, | ||
| ) { | ||
| let mem_size = Bytes(heap_end - heap_base); | ||
|
|
||
|
|
@@ -83,20 +85,17 @@ unsafe fn mark_compact<M: Memory, SetHp: Fn(u32)>( | |
|
|
||
| mark_static_roots(mem, static_roots, heap_base); | ||
|
|
||
| if (*closure_table_loc).unskew() >= heap_base as usize { | ||
| mark_object(mem, *closure_table_loc, heap_base); | ||
| if (*closure_table_ptr_loc).unskew() >= heap_base as usize { | ||
| // TODO: No need to check if closure table is already marked | ||
| mark_object(mem, *closure_table_ptr_loc, heap_base); | ||
| // Similar to `mark_root_mutbox_fields`, `closure_table_ptr_loc` is in static heap so it | ||
| // will be readable when we unthread closure table | ||
| thread(closure_table_ptr_loc); | ||
| } | ||
|
|
||
| mark_stack(mem, heap_base); | ||
|
|
||
| thread_roots(static_roots, heap_base); | ||
|
|
||
| if (*closure_table_loc).unskew() >= heap_base as usize { | ||
| thread(closure_table_loc); | ||
| } | ||
|
|
||
| update_fwd_refs(heap_base); | ||
| update_bwd_refs(set_hp, heap_base); | ||
| update_refs(set_hp, heap_base); | ||
|
|
||
| free_mark_stack(); | ||
| free_bitmap(); | ||
|
|
@@ -108,11 +107,15 @@ unsafe fn mark_static_roots<M: Memory>(mem: &mut M, static_roots: SkewedPtr, hea | |
| // Static objects are not in the dynamic heap so don't need marking. | ||
| for i in 0..root_array.len() { | ||
| let obj = root_array.get(i).unskew() as *mut Obj; | ||
| mark_fields(mem, obj, heap_base); | ||
| // Root array should only has pointers to other static MutBoxes | ||
| debug_assert_eq!(obj.tag(), TAG_MUTBOX); // check tag | ||
| debug_assert!((obj as u32) < heap_base); // check that MutBox is static | ||
| mark_root_mutbox_fields(mem, obj as *mut MutBox, heap_base); | ||
| } | ||
| } | ||
|
|
||
| unsafe fn mark_object<M: Memory>(mem: &mut M, obj: SkewedPtr, heap_base: u32) { | ||
| let obj_tag = obj.tag(); | ||
| let obj = obj.unskew() as u32; | ||
|
|
||
| let obj_idx = (obj - heap_base) / WORD_SIZE; | ||
|
|
@@ -123,87 +126,89 @@ unsafe fn mark_object<M: Memory>(mem: &mut M, obj: SkewedPtr, heap_base: u32) { | |
| } | ||
|
|
||
| set_bit(obj_idx); | ||
| push_mark_stack(mem, obj as usize); | ||
| push_mark_stack(mem, obj as usize, obj_tag); | ||
| } | ||
|
|
||
| unsafe fn mark_stack<M: Memory>(mem: &mut M, heap_base: u32) { | ||
| while let Some(obj) = pop_mark_stack() { | ||
| mark_fields(mem, obj as *mut Obj, heap_base); | ||
| while let Some((obj, tag)) = pop_mark_stack() { | ||
| mark_fields(mem, obj as *mut Obj, tag, heap_base); | ||
| } | ||
| } | ||
|
|
||
| unsafe fn mark_fields<M: Memory>(mem: &mut M, obj: *mut Obj, heap_base: u32) { | ||
| visit_pointer_fields(obj, heap_base as usize, |field_addr| { | ||
| mark_object(mem, *field_addr, heap_base); | ||
| }); | ||
| } | ||
| unsafe fn mark_fields<M: Memory>(mem: &mut M, obj: *mut Obj, obj_tag: Tag, heap_base: u32) { | ||
| visit_pointer_fields(obj, obj_tag, heap_base as usize, |field_addr| { | ||
| let field_value = *field_addr; | ||
| mark_object(mem, field_value, heap_base); | ||
|
|
||
| unsafe fn thread_roots(static_roots: SkewedPtr, heap_base: u32) { | ||
| // Static roots | ||
| let root_array = static_roots.as_array(); | ||
| for i in 0..root_array.len() { | ||
| thread_obj_fields(root_array.get(i).unskew() as *mut Obj, heap_base); | ||
| } | ||
| // No need to thread closure table here as it's on heap and we already marked it | ||
| // Thread if backwards pointer | ||
| if field_value.unskew() < obj as usize { | ||
| thread(field_addr); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| /// Scan the heap, update forward references. At the end of this pass all fields will be threaded | ||
| /// and forward references will be updated, pointing to the object's new location. | ||
| unsafe fn update_fwd_refs(heap_base: u32) { | ||
| let mut free = heap_base; | ||
|
|
||
| let mut bitmap_iter = iter_bits(); | ||
| let mut bit = bitmap_iter.next(); | ||
| while bit != BITMAP_ITER_END { | ||
| let p = (heap_base + (bit * WORD_SIZE)) as *mut Obj; | ||
|
|
||
| // Update forward references to the object to the object's new location and restore | ||
| // object header | ||
| unthread(p, free); | ||
|
|
||
| // Get the size before threading the fields, to handle self references. | ||
| let size = object_size(p as usize).to_bytes().0; | ||
|
|
||
| // Thread fields | ||
| thread_obj_fields(p, heap_base); | ||
|
|
||
| free += size; | ||
|
|
||
| bit = bitmap_iter.next(); | ||
| /// Specialized version of `mark_fields` for root `MutBox`es. | ||
| unsafe fn mark_root_mutbox_fields<M: Memory>(mem: &mut M, mutbox: *mut MutBox, heap_base: u32) { | ||
| let field_addr = &mut (*mutbox).field; | ||
| // TODO: Not sure if this check is necessary? | ||
| if pointer_to_dynamic_heap(field_addr, heap_base as usize) { | ||
| // TODO: We should be able to omit the "already marked" check here as no two root MutBox | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's probably true, but I'd check with Joachim first. Or just play it safe. Does the set of root boxes ever get large? My intuition is that it corresponds to top-level vars and recursive declarations that need back-patching, but I'm not sure. |
||
| // can point to the same object (I think) | ||
| mark_object(mem, *field_addr, heap_base); | ||
| // It's OK to thread forward pointers here as the static objects won't be moved, so we will | ||
| // be able to unthread objects pointed by these fields later. | ||
| thread(field_addr); | ||
| } | ||
| } | ||
|
|
||
| /// Expects all fields to be threaded. Updates backward references and moves objects to their new | ||
| /// locations. | ||
| unsafe fn update_bwd_refs<SetHp: Fn(u32)>(set_hp: SetHp, heap_base: u32) { | ||
| /// Linearly scan the heap, for each live object: | ||
| /// | ||
| /// - Mark step threads all backwards pointers and pointers from roots, so unthread to update those | ||
| /// pointers to the objects new location. | ||
| /// | ||
| /// - Move the object | ||
| /// | ||
| /// - Thread forward pointers of the object | ||
| /// | ||
| unsafe fn update_refs<SetHp: Fn(u32)>(set_hp: SetHp, heap_base: u32) { | ||
| let mut free = heap_base; | ||
|
|
||
| let mut bitmap_iter = iter_bits(); | ||
| let mut bit = bitmap_iter.next(); | ||
| while bit != BITMAP_ITER_END { | ||
| let p = (heap_base + (bit * WORD_SIZE)) as *mut Obj; | ||
| let p_new = free; | ||
|
|
||
| // Update backward references to the object's new location and restore object header | ||
| unthread(p, free); | ||
| // Update backwards references to the object's new location and restore object header | ||
| unthread(p, p_new); | ||
|
|
||
| // All references to the object now point to the new location, move the object | ||
| // Move the object | ||
| let p_size_words = object_size(p as usize); | ||
| if free as usize != p as usize { | ||
| memcpy_words(free as usize, p as usize, p_size_words); | ||
| if p_new as usize != p as usize { | ||
| memcpy_words(p_new as usize, p as usize, p_size_words); | ||
| } | ||
|
|
||
| free += p_size_words.to_bytes().0; | ||
|
|
||
| // Thread forward pointers of the object | ||
| thread_fwd_pointers(p_new as *mut Obj, heap_base); | ||
osa1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| bit = bitmap_iter.next(); | ||
| } | ||
|
|
||
| set_hp(free); | ||
| } | ||
|
|
||
| unsafe fn thread_obj_fields(obj: *mut Obj, heap_base: u32) { | ||
| visit_pointer_fields(obj, heap_base as usize, |field_addr| thread(field_addr)); | ||
| /// Thread forwards pointers in object | ||
| unsafe fn thread_fwd_pointers(obj: *mut Obj, heap_base: u32) { | ||
| visit_pointer_fields(obj, obj.tag(), heap_base as usize, |field_addr| { | ||
| if (*field_addr).unskew() > field_addr as usize { | ||
| thread(field_addr) | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| /// Thread a pointer field | ||
| unsafe fn thread(field: *mut SkewedPtr) { | ||
| // Store pointed object's header in the field, field address in the pointed object's header | ||
| let pointed = (*field).unskew() as *mut Obj; | ||
|
|
@@ -212,7 +217,7 @@ unsafe fn thread(field: *mut SkewedPtr) { | |
| (*pointed).tag = field as u32; | ||
| } | ||
|
|
||
| /// Unthread all references, replacing with `new_loc` | ||
| /// Unthread all references at given header, replacing with `new_loc`. Restores object header. | ||
| unsafe fn unthread(obj: *mut Obj, new_loc: u32) { | ||
| // NOTE: For this to work heap addresses need to be greater than the largest value for object | ||
| // headers. Currently this holds. TODO: Document this better. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
I though I had commented on this, but perhaps I forgot to submit.
Is there any danger you thread the same field twice, if you've already visited the object?
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.
We should not visit an object twice. This function is called on values popped from mark stack, and we mark objects when pushing to mark stack and do not push marked objects to the mark stack. So a field should only be threaded once.